@aztec/blob-client 0.0.1-commit.e0f15ab9b → 0.0.1-commit.e304674f1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -42,6 +42,10 @@ export interface BlobClientConfig extends BlobArchiveApiConfig {
42
42
  blobHealthcheckUploadIntervalMinutes?: number;
43
43
  /** Timeout for HTTP requests to the L1 RPC node in ms. */
44
44
  l1HttpTimeoutMS?: number;
45
+ /** Whether to prefer filestores over consensus clients when fetching blobs. Default: false (consensus first). */
46
+ blobPreferFilestores?: boolean;
47
+ /** Timeout in ms for HTTP requests to the blob file store. Default: 10000 (10s). */
48
+ blobFileStoreTimeoutMs?: number;
45
49
  }
46
50
  export declare const blobClientConfigMapping: ConfigMappingsType<BlobClientConfig>;
47
51
  /**
@@ -53,4 +57,4 @@ export declare function getBlobClientConfigFromEnv(): BlobClientConfig;
53
57
  * Returns whether the given blob client config has any remote sources defined.
54
58
  */
55
59
  export declare function hasRemoteBlobSources(config?: BlobClientConfig): boolean;
56
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29uZmlnLmQudHMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvY2xpZW50L2NvbmZpZy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQ0wsS0FBSyxrQkFBa0IsRUFDdkIsV0FBVyxFQUlaLE1BQU0sMEJBQTBCLENBQUM7QUFFbEMsT0FBTyxFQUFFLEtBQUssb0JBQW9CLEVBQWdDLE1BQU0sc0JBQXNCLENBQUM7QUFFL0Y7O0dBRUc7QUFDSCxNQUFNLFdBQVcsZ0JBQWlCLFNBQVEsb0JBQW9CO0lBQzVEOztPQUVHO0lBQ0gsU0FBUyxDQUFDLEVBQUUsTUFBTSxFQUFFLENBQUM7SUFFckI7O09BRUc7SUFDSCxtQkFBbUIsQ0FBQyxFQUFFLE1BQU0sRUFBRSxDQUFDO0lBRS9COztPQUVHO0lBQ0gsc0JBQXNCLENBQUMsRUFBRSxXQUFXLENBQUMsTUFBTSxDQUFDLEVBQUUsQ0FBQztJQUUvQzs7T0FFRztJQUNILDRCQUE0QixDQUFDLEVBQUUsTUFBTSxFQUFFLENBQUM7SUFFeEM7O09BRUc7SUFDSCxpQkFBaUIsQ0FBQyxFQUFFLE1BQU0sQ0FBQztJQUUzQjs7T0FFRztJQUNILHFCQUFxQixDQUFDLEVBQUUsT0FBTyxDQUFDO0lBRWhDOztPQUVHO0lBQ0gsaUJBQWlCLENBQUMsRUFBRSxNQUFNLEVBQUUsQ0FBQztJQUU3Qjs7T0FFRztJQUNILHNCQUFzQixDQUFDLEVBQUUsTUFBTSxDQUFDO0lBRWhDOztPQUVHO0lBQ0gsb0NBQW9DLENBQUMsRUFBRSxNQUFNLENBQUM7SUFFOUMsMERBQTBEO0lBQzFELGVBQWUsQ0FBQyxFQUFFLE1BQU0sQ0FBQztDQUMxQjtBQUVELGVBQU8sTUFBTSx1QkFBdUIsRUFBRSxrQkFBa0IsQ0FBQyxnQkFBZ0IsQ0F5RHhFLENBQUM7QUFFRjs7O0dBR0c7QUFDSCx3QkFBZ0IsMEJBQTBCLElBQUksZ0JBQWdCLENBRTdEO0FBRUQ7O0dBRUc7QUFDSCx3QkFBZ0Isb0JBQW9CLENBQUMsTUFBTSxHQUFFLGdCQUFxQixHQUFHLE9BQU8sQ0FFM0UifQ==
60
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29uZmlnLmQudHMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvY2xpZW50L2NvbmZpZy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQ0wsS0FBSyxrQkFBa0IsRUFDdkIsV0FBVyxFQUlaLE1BQU0sMEJBQTBCLENBQUM7QUFFbEMsT0FBTyxFQUFFLEtBQUssb0JBQW9CLEVBQWdDLE1BQU0sc0JBQXNCLENBQUM7QUFFL0Y7O0dBRUc7QUFDSCxNQUFNLFdBQVcsZ0JBQWlCLFNBQVEsb0JBQW9CO0lBQzVEOztPQUVHO0lBQ0gsU0FBUyxDQUFDLEVBQUUsTUFBTSxFQUFFLENBQUM7SUFFckI7O09BRUc7SUFDSCxtQkFBbUIsQ0FBQyxFQUFFLE1BQU0sRUFBRSxDQUFDO0lBRS9COztPQUVHO0lBQ0gsc0JBQXNCLENBQUMsRUFBRSxXQUFXLENBQUMsTUFBTSxDQUFDLEVBQUUsQ0FBQztJQUUvQzs7T0FFRztJQUNILDRCQUE0QixDQUFDLEVBQUUsTUFBTSxFQUFFLENBQUM7SUFFeEM7O09BRUc7SUFDSCxpQkFBaUIsQ0FBQyxFQUFFLE1BQU0sQ0FBQztJQUUzQjs7T0FFRztJQUNILHFCQUFxQixDQUFDLEVBQUUsT0FBTyxDQUFDO0lBRWhDOztPQUVHO0lBQ0gsaUJBQWlCLENBQUMsRUFBRSxNQUFNLEVBQUUsQ0FBQztJQUU3Qjs7T0FFRztJQUNILHNCQUFzQixDQUFDLEVBQUUsTUFBTSxDQUFDO0lBRWhDOztPQUVHO0lBQ0gsb0NBQW9DLENBQUMsRUFBRSxNQUFNLENBQUM7SUFFOUMsMERBQTBEO0lBQzFELGVBQWUsQ0FBQyxFQUFFLE1BQU0sQ0FBQztJQUV6QixpSEFBaUg7SUFDakgsb0JBQW9CLENBQUMsRUFBRSxPQUFPLENBQUM7SUFFL0Isb0ZBQW9GO0lBQ3BGLHNCQUFzQixDQUFDLEVBQUUsTUFBTSxDQUFDO0NBQ2pDO0FBRUQsZUFBTyxNQUFNLHVCQUF1QixFQUFFLGtCQUFrQixDQUFDLGdCQUFnQixDQW1FeEUsQ0FBQztBQUVGOzs7R0FHRztBQUNILHdCQUFnQiwwQkFBMEIsSUFBSSxnQkFBZ0IsQ0FFN0Q7QUFFRDs7R0FFRztBQUNILHdCQUFnQixvQkFBb0IsQ0FBQyxNQUFNLEdBQUUsZ0JBQXFCLEdBQUcsT0FBTyxDQUUzRSJ9
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/client/config.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,kBAAkB,EACvB,WAAW,EAIZ,MAAM,0BAA0B,CAAC;AAElC,OAAO,EAAE,KAAK,oBAAoB,EAAgC,MAAM,sBAAsB,CAAC;AAE/F;;GAEG;AACH,MAAM,WAAW,gBAAiB,SAAQ,oBAAoB;IAC5D;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IAErB;;OAEG;IACH,mBAAmB,CAAC,EAAE,MAAM,EAAE,CAAC;IAE/B;;OAEG;IACH,sBAAsB,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,EAAE,CAAC;IAE/C;;OAEG;IACH,4BAA4B,CAAC,EAAE,MAAM,EAAE,CAAC;IAExC;;OAEG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAE3B;;OAEG;IACH,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAEhC;;OAEG;IACH,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;IAE7B;;OAEG;IACH,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAEhC;;OAEG;IACH,oCAAoC,CAAC,EAAE,MAAM,CAAC;IAE9C,0DAA0D;IAC1D,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,eAAO,MAAM,uBAAuB,EAAE,kBAAkB,CAAC,gBAAgB,CAyDxE,CAAC;AAEF;;;GAGG;AACH,wBAAgB,0BAA0B,IAAI,gBAAgB,CAE7D;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,GAAE,gBAAqB,GAAG,OAAO,CAE3E"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/client/config.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,kBAAkB,EACvB,WAAW,EAIZ,MAAM,0BAA0B,CAAC;AAElC,OAAO,EAAE,KAAK,oBAAoB,EAAgC,MAAM,sBAAsB,CAAC;AAE/F;;GAEG;AACH,MAAM,WAAW,gBAAiB,SAAQ,oBAAoB;IAC5D;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IAErB;;OAEG;IACH,mBAAmB,CAAC,EAAE,MAAM,EAAE,CAAC;IAE/B;;OAEG;IACH,sBAAsB,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,EAAE,CAAC;IAE/C;;OAEG;IACH,4BAA4B,CAAC,EAAE,MAAM,EAAE,CAAC;IAExC;;OAEG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAE3B;;OAEG;IACH,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAEhC;;OAEG;IACH,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;IAE7B;;OAEG;IACH,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAEhC;;OAEG;IACH,oCAAoC,CAAC,EAAE,MAAM,CAAC;IAE9C,0DAA0D;IAC1D,eAAe,CAAC,EAAE,MAAM,CAAC;IAEzB,iHAAiH;IACjH,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAE/B,oFAAoF;IACpF,sBAAsB,CAAC,EAAE,MAAM,CAAC;CACjC;AAED,eAAO,MAAM,uBAAuB,EAAE,kBAAkB,CAAC,gBAAgB,CAmExE,CAAC;AAEF;;;GAGG;AACH,wBAAgB,0BAA0B,IAAI,gBAAgB,CAE7D;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,GAAE,gBAAqB,GAAG,OAAO,CAE3E"}
@@ -50,6 +50,16 @@ export const blobClientConfigMapping = {
50
50
  description: 'Timeout for HTTP requests to the L1 RPC node in ms.',
51
51
  ...optionalNumberConfigHelper()
52
52
  },
53
+ blobPreferFilestores: {
54
+ env: 'BLOB_PREFER_FILESTORES',
55
+ description: 'Whether to prefer filestores over consensus clients when fetching blobs. Default: false.',
56
+ ...booleanConfigHelper(false)
57
+ },
58
+ blobFileStoreTimeoutMs: {
59
+ env: 'BLOB_FILE_STORE_TIMEOUT_MS',
60
+ description: 'Timeout in ms for HTTP requests to the blob file store. Default: 10000 (10s).',
61
+ ...optionalNumberConfigHelper()
62
+ },
53
63
  ...blobArchiveApiConfigMappings
54
64
  };
55
65
  /**
@@ -37,4 +37,4 @@ export interface BlobClientWithFileStoresConfig extends BlobClientConfig {
37
37
  * @returns A BlobClientInterface configured with file store support, already started
38
38
  */
39
39
  export declare function createBlobClientWithFileStores(config: BlobClientWithFileStoresConfig, logger?: Logger): Promise<BlobClientInterface>;
40
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZmFjdG9yeS5kLnRzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2NsaWVudC9mYWN0b3J5LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxLQUFLLE1BQU0sRUFBZ0IsTUFBTSx1QkFBdUIsQ0FBQztBQVFsRSxPQUFPLEtBQUssRUFBRSxtQkFBbUIsRUFBRSxNQUFNLHVDQUF1QyxDQUFDO0FBQ2pGLE9BQU8sRUFBRSxLQUFLLGdCQUFnQixFQUF3QixNQUFNLGFBQWEsQ0FBQztBQUUxRSxPQUFPLEtBQUssRUFBRSxtQkFBbUIsRUFBRSxNQUFNLGdCQUFnQixDQUFDO0FBRzFELE1BQU0sV0FBVyxvQkFBb0I7SUFDbkMsTUFBTSxDQUFDLEVBQUUsTUFBTSxDQUFDO0lBQ2hCLDBDQUEwQztJQUMxQyxnQkFBZ0IsQ0FBQyxFQUFFLG1CQUFtQixFQUFFLENBQUM7SUFDekMsMkNBQTJDO0lBQzNDLHFCQUFxQixDQUFDLEVBQUUsbUJBQW1CLENBQUM7Q0FDN0M7QUFFRCx3QkFBZ0IsZ0JBQWdCLENBQUMsTUFBTSxDQUFDLEVBQUUsZ0JBQWdCLEVBQUUsSUFBSSxDQUFDLEVBQUUsb0JBQW9CLEdBQUcsbUJBQW1CLENBbUI1RztBQUVEOzs7R0FHRztBQUNILE1BQU0sV0FBVyw4QkFBK0IsU0FBUSxnQkFBZ0I7SUFDdEUsU0FBUyxFQUFFLE1BQU0sQ0FBQztJQUNsQixhQUFhLEVBQUUsTUFBTSxDQUFDO0lBQ3RCLFdBQVcsRUFBRTtRQUFFLGFBQWEsRUFBRTtZQUFFLFFBQVEsSUFBSSxNQUFNLENBQUE7U0FBRSxDQUFBO0tBQUUsQ0FBQztDQUN4RDtBQUVEOzs7Ozs7Ozs7Ozs7R0FZRztBQUNILHdCQUFzQiw4QkFBOEIsQ0FDbEQsTUFBTSxFQUFFLDhCQUE4QixFQUN0QyxNQUFNLENBQUMsRUFBRSxNQUFNLEdBQ2QsT0FBTyxDQUFDLG1CQUFtQixDQUFDLENBdUI5QiJ9
40
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZmFjdG9yeS5kLnRzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2NsaWVudC9mYWN0b3J5LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxLQUFLLE1BQU0sRUFBZ0IsTUFBTSx1QkFBdUIsQ0FBQztBQVFsRSxPQUFPLEtBQUssRUFBRSxtQkFBbUIsRUFBRSxNQUFNLHVDQUF1QyxDQUFDO0FBQ2pGLE9BQU8sRUFBRSxLQUFLLGdCQUFnQixFQUF3QixNQUFNLGFBQWEsQ0FBQztBQUUxRSxPQUFPLEtBQUssRUFBRSxtQkFBbUIsRUFBRSxNQUFNLGdCQUFnQixDQUFDO0FBRzFELE1BQU0sV0FBVyxvQkFBb0I7SUFDbkMsTUFBTSxDQUFDLEVBQUUsTUFBTSxDQUFDO0lBQ2hCLDBDQUEwQztJQUMxQyxnQkFBZ0IsQ0FBQyxFQUFFLG1CQUFtQixFQUFFLENBQUM7SUFDekMsMkNBQTJDO0lBQzNDLHFCQUFxQixDQUFDLEVBQUUsbUJBQW1CLENBQUM7Q0FDN0M7QUFFRCx3QkFBZ0IsZ0JBQWdCLENBQUMsTUFBTSxDQUFDLEVBQUUsZ0JBQWdCLEVBQUUsSUFBSSxDQUFDLEVBQUUsb0JBQW9CLEdBQUcsbUJBQW1CLENBbUI1RztBQUVEOzs7R0FHRztBQUNILE1BQU0sV0FBVyw4QkFBK0IsU0FBUSxnQkFBZ0I7SUFDdEUsU0FBUyxFQUFFLE1BQU0sQ0FBQztJQUNsQixhQUFhLEVBQUUsTUFBTSxDQUFDO0lBQ3RCLFdBQVcsRUFBRTtRQUFFLGFBQWEsRUFBRTtZQUFFLFFBQVEsSUFBSSxNQUFNLENBQUE7U0FBRSxDQUFBO0tBQUUsQ0FBQztDQUN4RDtBQUVEOzs7Ozs7Ozs7Ozs7R0FZRztBQUNILHdCQUFzQiw4QkFBOEIsQ0FDbEQsTUFBTSxFQUFFLDhCQUE4QixFQUN0QyxNQUFNLENBQUMsRUFBRSxNQUFNLEdBQ2QsT0FBTyxDQUFDLG1CQUFtQixDQUFDLENBOEI5QiJ9
@@ -1 +1 @@
1
- {"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../../src/client/factory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,MAAM,EAAgB,MAAM,uBAAuB,CAAC;AAQlE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,uCAAuC,CAAC;AACjF,OAAO,EAAE,KAAK,gBAAgB,EAAwB,MAAM,aAAa,CAAC;AAE1E,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAG1D,MAAM,WAAW,oBAAoB;IACnC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,0CAA0C;IAC1C,gBAAgB,CAAC,EAAE,mBAAmB,EAAE,CAAC;IACzC,2CAA2C;IAC3C,qBAAqB,CAAC,EAAE,mBAAmB,CAAC;CAC7C;AAED,wBAAgB,gBAAgB,CAAC,MAAM,CAAC,EAAE,gBAAgB,EAAE,IAAI,CAAC,EAAE,oBAAoB,GAAG,mBAAmB,CAmB5G;AAED;;;GAGG;AACH,MAAM,WAAW,8BAA+B,SAAQ,gBAAgB;IACtE,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE;QAAE,aAAa,EAAE;YAAE,QAAQ,IAAI,MAAM,CAAA;SAAE,CAAA;KAAE,CAAC;CACxD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,8BAA8B,CAClD,MAAM,EAAE,8BAA8B,EACtC,MAAM,CAAC,EAAE,MAAM,GACd,OAAO,CAAC,mBAAmB,CAAC,CAuB9B"}
1
+ {"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../../src/client/factory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,MAAM,EAAgB,MAAM,uBAAuB,CAAC;AAQlE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,uCAAuC,CAAC;AACjF,OAAO,EAAE,KAAK,gBAAgB,EAAwB,MAAM,aAAa,CAAC;AAE1E,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAG1D,MAAM,WAAW,oBAAoB;IACnC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,0CAA0C;IAC1C,gBAAgB,CAAC,EAAE,mBAAmB,EAAE,CAAC;IACzC,2CAA2C;IAC3C,qBAAqB,CAAC,EAAE,mBAAmB,CAAC;CAC7C;AAED,wBAAgB,gBAAgB,CAAC,MAAM,CAAC,EAAE,gBAAgB,EAAE,IAAI,CAAC,EAAE,oBAAoB,GAAG,mBAAmB,CAmB5G;AAED;;;GAGG;AACH,MAAM,WAAW,8BAA+B,SAAQ,gBAAgB;IACtE,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE;QAAE,aAAa,EAAE;YAAE,QAAQ,IAAI,MAAM,CAAA;SAAE,CAAA;KAAE,CAAC;CACxD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,8BAA8B,CAClD,MAAM,EAAE,8BAA8B,EACtC,MAAM,CAAC,EAAE,MAAM,GACd,OAAO,CAAC,mBAAmB,CAAC,CA8B9B"}
@@ -42,8 +42,14 @@ export function createBlobClient(config, deps) {
42
42
  rollupVersion: config.rollupVersion,
43
43
  rollupAddress: config.l1Contracts.rollupAddress.toString()
44
44
  };
45
+ // Disable internal retries for blob file stores — retry logic is handled by HttpBlobClient.
46
+ // Set a configurable timeout (default 10s) to avoid hanging on slow stores.
47
+ const httpOptions = {
48
+ retryBackoff: [],
49
+ timeoutMs: config.blobFileStoreTimeoutMs ?? 10_000
50
+ };
45
51
  const [fileStoreClients, fileStoreUploadClient] = await Promise.all([
46
- createReadOnlyFileStoreBlobClients(config.blobFileStoreUrls, fileStoreMetadata, log),
52
+ createReadOnlyFileStoreBlobClients(config.blobFileStoreUrls, fileStoreMetadata, log, httpOptions),
47
53
  createWritableFileStoreBlobClient(config.blobFileStoreUploadUrl, fileStoreMetadata, log)
48
54
  ]);
49
55
  const client = createBlobClient(config, {
@@ -18,6 +18,8 @@ export declare class HttpBlobClient implements BlobClientInterface {
18
18
  private beaconGenesisTime?;
19
19
  /** Cached beacon slot duration in seconds. Fetched once at startup. */
20
20
  private beaconSecondsPerSlot?;
21
+ /** Indexes of consensus hosts that serve blob sidecars (supernodes). Populated by testSources(). */
22
+ private superNodeHostIndexes?;
21
23
  constructor(config?: BlobClientConfig, opts?: {
22
24
  logger?: Logger;
23
25
  archiveClient?: BlobArchiveClient;
@@ -41,21 +43,21 @@ export declare class HttpBlobClient implements BlobClientInterface {
41
43
  testSources(): Promise<void>;
42
44
  sendBlobsToFilestore(blobs: Blob[]): Promise<boolean>;
43
45
  /**
44
- * Get the blob sidecar
46
+ * Get the blob sidecar.
45
47
  *
46
- * If requesting from the blob client, we send the blobkHash
47
- * If requesting from the beacon node, we send the slot number
48
- *
49
- * Source ordering depends on sync state:
50
- * - Historical sync: blob client → FileStore → L1 consensus → Archive
51
- * - Near tip sync: blob client → FileStore → L1 consensus → FileStore (with retries) → Archive (eg blobscan)
48
+ * Alternates between two primary sources (consensus and filestore) in a retry loop,
49
+ * then falls back to archive if blobs are still missing. The order of the primary
50
+ * sources is configurable via `blobPreferFilestores`.
52
51
  *
53
52
  * @param blockHash - The block hash
54
53
  * @param blobHashes - The blob hashes to fetch
55
- * @param opts - Options including isHistoricalSync flag
54
+ * @param opts - Options for slot resolution
56
55
  * @returns The blobs
57
56
  */
58
57
  getBlobSidecar(blockHash: `0x${string}`, blobHashes: Buffer[], opts?: GetBlobSidecarOptions): Promise<Blob[]>;
58
+ /** Resolves the beacon slot number for the given block hash. Returns undefined if no consensus hosts. */
59
+ private resolveSlotNumber;
60
+ private tryConsensusHosts;
59
61
  private tryFileStores;
60
62
  getBlobSidecarFrom(hostUrl: string, blockHashOrSlot: string | number, blobHashes?: Buffer[], l1ConsensusHostIndex?: number): Promise<Blob[]>;
61
63
  getBlobsFromHost(hostUrl: string, blockHashOrSlot: string | number, l1ConsensusHostIndex?: number, blobHashes?: Buffer[]): Promise<BlobJson[]>;
@@ -82,4 +84,4 @@ export declare class HttpBlobClient implements BlobClientInterface {
82
84
  */
83
85
  stop(): void;
84
86
  }
85
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaHR0cC5kLnRzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2NsaWVudC9odHRwLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxJQUFJLEVBQUUsS0FBSyxRQUFRLEVBQStCLE1BQU0saUJBQWlCLENBQUM7QUFHbkYsT0FBTyxFQUFFLEtBQUssTUFBTSxFQUFnQixNQUFNLHVCQUF1QixDQUFDO0FBT2xFLE9BQU8sS0FBSyxFQUFFLGlCQUFpQixFQUFFLE1BQU0seUJBQXlCLENBQUM7QUFDakUsT0FBTyxLQUFLLEVBQUUsbUJBQW1CLEVBQUUsTUFBTSx1Q0FBdUMsQ0FBQztBQUVqRixPQUFPLEVBQUUsS0FBSyxnQkFBZ0IsRUFBOEIsTUFBTSxhQUFhLENBQUM7QUFDaEYsT0FBTyxLQUFLLEVBQUUsbUJBQW1CLEVBQUUscUJBQXFCLEVBQUUsTUFBTSxnQkFBZ0IsQ0FBQztBQUVqRixxQkFBYSxjQUFlLFlBQVcsbUJBQW1CO0lBa0J0RCxPQUFPLENBQUMsUUFBUSxDQUFDLElBQUk7SUFqQnZCLFNBQVMsQ0FBQyxRQUFRLENBQUMsR0FBRyxFQUFFLE1BQU0sQ0FBQztJQUMvQixTQUFTLENBQUMsUUFBUSxDQUFDLE1BQU0sRUFBRSxnQkFBZ0IsQ0FBQztJQUM1QyxTQUFTLENBQUMsUUFBUSxDQUFDLGFBQWEsRUFBRSxpQkFBaUIsR0FBRyxTQUFTLENBQUM7SUFDaEUsU0FBUyxDQUFDLFFBQVEsQ0FBQyxLQUFLLEVBQUUsT0FBTyxLQUFLLENBQUM7SUFDdkMsU0FBUyxDQUFDLFFBQVEsQ0FBQyxnQkFBZ0IsRUFBRSxtQkFBbUIsRUFBRSxDQUFDO0lBQzNELFNBQVMsQ0FBQyxRQUFRLENBQUMscUJBQXFCLEVBQUUsbUJBQW1CLEdBQUcsU0FBUyxDQUFDO0lBRTFFLE9BQU8sQ0FBQyxRQUFRLENBQVM7SUFDekIsT0FBTyxDQUFDLDJCQUEyQixDQUFDLENBQWlCO0lBRXJELHNGQUFzRjtJQUN0RixPQUFPLENBQUMsaUJBQWlCLENBQUMsQ0FBUztJQUNuQyx1RUFBdUU7SUFDdkUsT0FBTyxDQUFDLG9CQUFvQixDQUFDLENBQVM7SUFFdEMsWUFDRSxNQUFNLENBQUMsRUFBRSxnQkFBZ0IsRUFDUixJQUFJLEdBQUU7UUFDckIsTUFBTSxDQUFDLEVBQUUsTUFBTSxDQUFDO1FBQ2hCLGFBQWEsQ0FBQyxFQUFFLGlCQUFpQixDQUFDO1FBQ2xDLGdCQUFnQixDQUFDLEVBQUUsbUJBQW1CLEVBQUUsQ0FBQztRQUN6QyxxQkFBcUIsQ0FBQyxFQUFFLG1CQUFtQixDQUFDO1FBQzVDLHlFQUF5RTtRQUN6RSxjQUFjLENBQUMsRUFBRSxDQUFDLEtBQUssRUFBRSxJQUFJLEVBQUUsS0FBSyxJQUFJLENBQUM7S0FDckMsRUF3QlA7SUFFRDs7O09BR0c7SUFDSCxPQUFPLENBQUMsc0JBQXNCO0lBVTlCOzs7OztPQUtHO0lBQ0ksV0FBVyxDQUFDLEtBQUssRUFBRSxPQUFPLEdBQUcsSUFBSSxDQUd2QztJQUVZLFdBQVcsa0JBd0d2QjtJQUVZLG9CQUFvQixDQUFDLEtBQUssRUFBRSxJQUFJLEVBQUUsR0FBRyxPQUFPLENBQUMsT0FBTyxDQUFDLENBbUJqRTtJQUVEOzs7Ozs7Ozs7Ozs7OztPQWNHO0lBQ1UsY0FBYyxDQUN6QixTQUFTLEVBQUUsS0FBSyxNQUFNLEVBQUUsRUFDeEIsVUFBVSxFQUFFLE1BQU0sRUFBRSxFQUNwQixJQUFJLENBQUMsRUFBRSxxQkFBcUIsR0FDM0IsT0FBTyxDQUFDLElBQUksRUFBRSxDQUFDLENBa0pqQjtZQVFhLGFBQWE7SUFzQ2Qsa0JBQWtCLENBQzdCLE9BQU8sRUFBRSxNQUFNLEVBQ2YsZUFBZSxFQUFFLE1BQU0sR0FBRyxNQUFNLEVBQ2hDLFVBQVUsR0FBRSxNQUFNLEVBQU8sRUFDekIsb0JBQW9CLENBQUMsRUFBRSxNQUFNLEdBQzVCLE9BQU8sQ0FBQyxJQUFJLEVBQUUsQ0FBQyxDQUdqQjtJQUVZLGdCQUFnQixDQUMzQixPQUFPLEVBQUUsTUFBTSxFQUNmLGVBQWUsRUFBRSxNQUFNLEdBQUcsTUFBTSxFQUNoQyxvQkFBb0IsQ0FBQyxFQUFFLE1BQU0sRUFDN0IsVUFBVSxDQUFDLEVBQUUsTUFBTSxFQUFFLEdBQ3BCLE9BQU8sQ0FBQyxRQUFRLEVBQUUsQ0FBQyxDQXNDckI7SUFFRCxPQUFPLENBQUMsaUJBQWlCO1lBcUJYLG1CQUFtQjtZQW1DbkIsYUFBYTtJQTZFM0Isc0NBQXNDO0lBQy9CLGdCQUFnQixJQUFJLGlCQUFpQixHQUFHLFNBQVMsQ0FFdkQ7SUFFRCxpRUFBaUU7SUFDMUQsU0FBUyxJQUFJLE9BQU8sQ0FFMUI7SUFFRDs7OztPQUlHO0lBQ1UsS0FBSyxJQUFJLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FXbEM7SUFFRDs7T0FFRztJQUNILE9BQU8sQ0FBQyw4QkFBOEI7WUFnQnhCLGlCQUFpQjtJQTBDL0I7O09BRUc7SUFDSSxJQUFJLElBQUksSUFBSSxDQUtsQjtDQUNGIn0=
87
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaHR0cC5kLnRzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2NsaWVudC9odHRwLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxJQUFJLEVBQUUsS0FBSyxRQUFRLEVBQStCLE1BQU0saUJBQWlCLENBQUM7QUFHbkYsT0FBTyxFQUFFLEtBQUssTUFBTSxFQUFnQixNQUFNLHVCQUF1QixDQUFDO0FBT2xFLE9BQU8sS0FBSyxFQUFFLGlCQUFpQixFQUFFLE1BQU0seUJBQXlCLENBQUM7QUFDakUsT0FBTyxLQUFLLEVBQUUsbUJBQW1CLEVBQUUsTUFBTSx1Q0FBdUMsQ0FBQztBQUVqRixPQUFPLEVBQUUsS0FBSyxnQkFBZ0IsRUFBOEIsTUFBTSxhQUFhLENBQUM7QUFDaEYsT0FBTyxLQUFLLEVBQUUsbUJBQW1CLEVBQUUscUJBQXFCLEVBQUUsTUFBTSxnQkFBZ0IsQ0FBQztBQUVqRixxQkFBYSxjQUFlLFlBQVcsbUJBQW1CO0lBcUJ0RCxPQUFPLENBQUMsUUFBUSxDQUFDLElBQUk7SUFwQnZCLFNBQVMsQ0FBQyxRQUFRLENBQUMsR0FBRyxFQUFFLE1BQU0sQ0FBQztJQUMvQixTQUFTLENBQUMsUUFBUSxDQUFDLE1BQU0sRUFBRSxnQkFBZ0IsQ0FBQztJQUM1QyxTQUFTLENBQUMsUUFBUSxDQUFDLGFBQWEsRUFBRSxpQkFBaUIsR0FBRyxTQUFTLENBQUM7SUFDaEUsU0FBUyxDQUFDLFFBQVEsQ0FBQyxLQUFLLEVBQUUsT0FBTyxLQUFLLENBQUM7SUFDdkMsU0FBUyxDQUFDLFFBQVEsQ0FBQyxnQkFBZ0IsRUFBRSxtQkFBbUIsRUFBRSxDQUFDO0lBQzNELFNBQVMsQ0FBQyxRQUFRLENBQUMscUJBQXFCLEVBQUUsbUJBQW1CLEdBQUcsU0FBUyxDQUFDO0lBRTFFLE9BQU8sQ0FBQyxRQUFRLENBQVM7SUFDekIsT0FBTyxDQUFDLDJCQUEyQixDQUFDLENBQWlCO0lBRXJELHNGQUFzRjtJQUN0RixPQUFPLENBQUMsaUJBQWlCLENBQUMsQ0FBUztJQUNuQyx1RUFBdUU7SUFDdkUsT0FBTyxDQUFDLG9CQUFvQixDQUFDLENBQVM7SUFFdEMsb0dBQW9HO0lBQ3BHLE9BQU8sQ0FBQyxvQkFBb0IsQ0FBQyxDQUFjO0lBRTNDLFlBQ0UsTUFBTSxDQUFDLEVBQUUsZ0JBQWdCLEVBQ1IsSUFBSSxHQUFFO1FBQ3JCLE1BQU0sQ0FBQyxFQUFFLE1BQU0sQ0FBQztRQUNoQixhQUFhLENBQUMsRUFBRSxpQkFBaUIsQ0FBQztRQUNsQyxnQkFBZ0IsQ0FBQyxFQUFFLG1CQUFtQixFQUFFLENBQUM7UUFDekMscUJBQXFCLENBQUMsRUFBRSxtQkFBbUIsQ0FBQztRQUM1Qyx5RUFBeUU7UUFDekUsY0FBYyxDQUFDLEVBQUUsQ0FBQyxLQUFLLEVBQUUsSUFBSSxFQUFFLEtBQUssSUFBSSxDQUFDO0tBQ3JDLEVBd0JQO0lBRUQ7OztPQUdHO0lBQ0gsT0FBTyxDQUFDLHNCQUFzQjtJQVU5Qjs7Ozs7T0FLRztJQUNJLFdBQVcsQ0FBQyxLQUFLLEVBQUUsT0FBTyxHQUFHLElBQUksQ0FHdkM7SUFFWSxXQUFXLGtCQStHdkI7SUFFWSxvQkFBb0IsQ0FBQyxLQUFLLEVBQUUsSUFBSSxFQUFFLEdBQUcsT0FBTyxDQUFDLE9BQU8sQ0FBQyxDQW1CakU7SUFFRDs7Ozs7Ozs7Ozs7T0FXRztJQUNVLGNBQWMsQ0FDekIsU0FBUyxFQUFFLEtBQUssTUFBTSxFQUFFLEVBQ3hCLFVBQVUsRUFBRSxNQUFNLEVBQUUsRUFDcEIsSUFBSSxDQUFDLEVBQUUscUJBQXFCLEdBQzNCLE9BQU8sQ0FBQyxJQUFJLEVBQUUsQ0FBQyxDQXlIakI7SUFFRCx5R0FBeUc7SUFDekcsT0FBTyxDQUFDLGlCQUFpQjtZQW1CWCxpQkFBaUI7WUFtRGpCLGFBQWE7SUFzQ2Qsa0JBQWtCLENBQzdCLE9BQU8sRUFBRSxNQUFNLEVBQ2YsZUFBZSxFQUFFLE1BQU0sR0FBRyxNQUFNLEVBQ2hDLFVBQVUsR0FBRSxNQUFNLEVBQU8sRUFDekIsb0JBQW9CLENBQUMsRUFBRSxNQUFNLEdBQzVCLE9BQU8sQ0FBQyxJQUFJLEVBQUUsQ0FBQyxDQUdqQjtJQUVZLGdCQUFnQixDQUMzQixPQUFPLEVBQUUsTUFBTSxFQUNmLGVBQWUsRUFBRSxNQUFNLEdBQUcsTUFBTSxFQUNoQyxvQkFBb0IsQ0FBQyxFQUFFLE1BQU0sRUFDN0IsVUFBVSxDQUFDLEVBQUUsTUFBTSxFQUFFLEdBQ3BCLE9BQU8sQ0FBQyxRQUFRLEVBQUUsQ0FBQyxDQXNDckI7SUFFRCxPQUFPLENBQUMsaUJBQWlCO1lBc0JYLG1CQUFtQjtZQW1DbkIsYUFBYTtJQTZFM0Isc0NBQXNDO0lBQy9CLGdCQUFnQixJQUFJLGlCQUFpQixHQUFHLFNBQVMsQ0FFdkQ7SUFFRCxpRUFBaUU7SUFDMUQsU0FBUyxJQUFJLE9BQU8sQ0FFMUI7SUFFRDs7OztPQUlHO0lBQ1UsS0FBSyxJQUFJLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FXbEM7SUFFRDs7T0FFRztJQUNILE9BQU8sQ0FBQyw4QkFBOEI7WUFnQnhCLGlCQUFpQjtJQTBDL0I7O09BRUc7SUFDSSxJQUFJLElBQUksSUFBSSxDQUtsQjtDQUNGIn0=
@@ -1 +1 @@
1
- {"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../src/client/http.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../src/client/http.ts"],"names":[],"mappings":"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"}
@@ -20,6 +20,7 @@ export class HttpBlobClient {
20
20
  healthcheckUploadIntervalId;
21
21
  /** Cached beacon genesis time (seconds since Unix epoch). Fetched once at startup. */ beaconGenesisTime;
22
22
  /** Cached beacon slot duration in seconds. Fetched once at startup. */ beaconSecondsPerSlot;
23
+ /** Indexes of consensus hosts that serve blob sidecars (supernodes). Populated by testSources(). */ superNodeHostIndexes;
23
24
  constructor(config, opts = {}){
24
25
  this.opts = opts;
25
26
  this.disabled = false;
@@ -73,6 +74,7 @@ export class HttpBlobClient {
73
74
  let consensusNonSuperNodes = 0;
74
75
  let archiveSources = 0;
75
76
  let blobSinks = 0;
77
+ const detectedSuperNodes = new Set();
76
78
  if (l1ConsensusHostUrls && l1ConsensusHostUrls.length > 0) {
77
79
  for(let l1ConsensusHostIndex = 0; l1ConsensusHostIndex < l1ConsensusHostUrls.length; l1ConsensusHostIndex++){
78
80
  const l1ConsensusHostUrl = l1ConsensusHostUrls[l1ConsensusHostIndex];
@@ -101,9 +103,10 @@ export class HttpBlobClient {
101
103
  this.log.info(`L1 consensus host serves blob sidecars (supernode)`, {
102
104
  l1ConsensusHostUrl
103
105
  });
106
+ detectedSuperNodes.add(l1ConsensusHostIndex);
104
107
  consensusSuperNodes++;
105
108
  } else {
106
- this.log.info(`L1 consensus host does not serve blob sidecars`, {
109
+ this.log.info(`L1 consensus host does not serve blob sidecars, skipping for blob fetching`, {
107
110
  l1ConsensusHostUrl
108
111
  });
109
112
  consensusNonSuperNodes++;
@@ -121,6 +124,7 @@ export class HttpBlobClient {
121
124
  }
122
125
  }
123
126
  }
127
+ this.superNodeHostIndexes = detectedSuperNodes;
124
128
  if (this.archiveClient) {
125
129
  try {
126
130
  const latest = await this.archiveClient.getLatestBlock();
@@ -193,29 +197,25 @@ export class HttpBlobClient {
193
197
  }
194
198
  }
195
199
  /**
196
- * Get the blob sidecar
200
+ * Get the blob sidecar.
197
201
  *
198
- * If requesting from the blob client, we send the blobkHash
199
- * If requesting from the beacon node, we send the slot number
200
- *
201
- * Source ordering depends on sync state:
202
- * - Historical sync: blob client → FileStore → L1 consensus → Archive
203
- * - Near tip sync: blob client → FileStore → L1 consensus → FileStore (with retries) → Archive (eg blobscan)
202
+ * Alternates between two primary sources (consensus and filestore) in a retry loop,
203
+ * then falls back to archive if blobs are still missing. The order of the primary
204
+ * sources is configurable via `blobPreferFilestores`.
204
205
  *
205
206
  * @param blockHash - The block hash
206
207
  * @param blobHashes - The blob hashes to fetch
207
- * @param opts - Options including isHistoricalSync flag
208
+ * @param opts - Options for slot resolution
208
209
  * @returns The blobs
209
210
  */ async getBlobSidecar(blockHash, blobHashes, opts) {
210
211
  if (this.disabled) {
211
212
  this.log.warn('Blob storage is disabled, returning empty blob sidecar');
212
213
  return [];
213
214
  }
214
- const isHistoricalSync = opts?.isHistoricalSync ?? false;
215
215
  // Accumulate blobs across sources, preserving order and handling duplicates
216
216
  // resultBlobs[i] will contain the blob for blobHashes[i], or undefined if not yet found
217
217
  const resultBlobs = new Array(blobHashes.length).fill(undefined);
218
- // Helper to get missing blob hashes that we still need to fetch
218
+ // Helper to get missing blob hashes that we still need to fetch
219
219
  const getMissingBlobHashes = ()=>blobHashes.map((bh, i)=>resultBlobs[i] === undefined ? bh : undefined).filter((bh)=>bh !== undefined);
220
220
  // Return the result, ignoring any undefined ones
221
221
  const getFilledBlobs = ()=>resultBlobs.filter((b)=>b !== undefined);
@@ -237,80 +237,69 @@ export class HttpBlobClient {
237
237
  }
238
238
  return blobs;
239
239
  };
240
- const { l1ConsensusHostUrls } = this.config;
241
240
  const ctx = {
242
241
  blockHash,
243
242
  blobHashes: blobHashes.map(bufferToHex)
244
243
  };
245
- // Try filestore (quick, no retries) - useful for both historical and near-tip sync
246
- if (this.fileStoreClients.length > 0 && getMissingBlobHashes().length > 0) {
247
- await this.tryFileStores(getMissingBlobHashes, fillResults, ctx);
248
- if (getMissingBlobHashes().length === 0) {
249
- return returnWithCallback(getFilledBlobs());
244
+ // Lazily resolve the slot number only resolved when consensus hosts are actually tried.
245
+ let slotNumber;
246
+ let slotResolved = false;
247
+ const getSlotNumber = async ()=>{
248
+ if (!slotResolved) {
249
+ slotNumber = await this.resolveSlotNumber(blockHash, opts);
250
+ slotResolved = true;
250
251
  }
251
- }
252
- const missingAfterSink = getMissingBlobHashes();
253
- if (missingAfterSink.length > 0 && l1ConsensusHostUrls && l1ConsensusHostUrls.length > 0) {
254
- // The beacon api can query by slot number, so we get that first
255
- const consensusCtx = {
256
- l1ConsensusHostUrls,
257
- ...ctx
258
- };
259
- this.log.trace(`Attempting to get slot number for block hash`, consensusCtx);
260
- const slotNumber = await this.getSlotNumber(blockHash, opts?.parentBeaconBlockRoot, opts?.l1BlockTimestamp);
261
- this.log.debug(`Got slot number ${slotNumber} from consensus host for querying blobs`, consensusCtx);
262
- if (slotNumber) {
263
- let l1ConsensusHostUrl;
264
- for(let l1ConsensusHostIndex = 0; l1ConsensusHostIndex < l1ConsensusHostUrls.length; l1ConsensusHostIndex++){
265
- const missingHashes = getMissingBlobHashes();
266
- if (missingHashes.length === 0) {
267
- break;
268
- }
269
- l1ConsensusHostUrl = l1ConsensusHostUrls[l1ConsensusHostIndex];
270
- this.log.trace(`Attempting to get ${missingHashes.length} blobs from consensus host`, {
271
- slotNumber,
272
- l1ConsensusHostUrl,
273
- ...ctx
274
- });
275
- const blobs = await this.getBlobsFromHost(l1ConsensusHostUrl, slotNumber, l1ConsensusHostIndex, getMissingBlobHashes());
276
- const result = await fillResults(blobs);
277
- this.log.debug(`Got ${blobs.length} blobs from consensus host (total: ${result.length}/${blobHashes.length})`, {
278
- slotNumber,
279
- l1ConsensusHostUrl,
280
- ...ctx
281
- });
282
- if (result.length === blobHashes.length) {
283
- return returnWithCallback(result);
284
- }
252
+ return slotNumber;
253
+ };
254
+ // Build the two source-try functions. The order depends on the config.
255
+ const tryConsensus = ()=>this.tryConsensusHosts(getSlotNumber, getMissingBlobHashes, fillResults, ctx);
256
+ const tryFilestores = ()=>this.tryFileStores(getMissingBlobHashes, fillResults, ctx);
257
+ const preferFilestores = this.config.blobPreferFilestores ?? false;
258
+ const [trySourceA, trySourceB] = preferFilestores ? [
259
+ tryFilestores,
260
+ tryConsensus
261
+ ] : [
262
+ tryConsensus,
263
+ tryFilestores
264
+ ];
265
+ // Historical sync: blobs should already exist, use shorter backoff for transient errors.
266
+ // Near-tip sync: blobs may still be uploading, use longer backoff for eventual consistency.
267
+ const isHistoricalSync = opts?.isHistoricalSync ?? false;
268
+ const backoff = isHistoricalSync ? [
269
+ 1,
270
+ 1
271
+ ] : [
272
+ 1,
273
+ 1,
274
+ 1,
275
+ 2,
276
+ 2
277
+ ];
278
+ // Retry loop: alternate between the two primary sources with backoff.
279
+ try {
280
+ await retry(async ()=>{
281
+ if (getMissingBlobHashes().length > 0) {
282
+ await trySourceA();
285
283
  }
286
- }
287
- }
288
- // For near-tip sync, retry filestores with backoff (eventual consistency)
289
- // This handles the case where blobs are still being uploaded by other validators
290
- if (!isHistoricalSync && this.fileStoreClients.length > 0 && getMissingBlobHashes().length > 0) {
291
- try {
292
- await retry(async ()=>{
293
- await this.tryFileStores(getMissingBlobHashes, fillResults, ctx);
294
- if (getMissingBlobHashes().length > 0) {
295
- throw new Error('Still missing blobs from filestores');
296
- }
297
- }, 'filestore blob retrieval', makeBackoff([
298
- 1,
299
- 1,
300
- 2
301
- ]), this.log, true);
302
- return returnWithCallback(getFilledBlobs());
303
- } catch {
304
- // Exhausted retries, continue to archive fallback
305
- }
306
- }
307
- const missingAfterConsensus = getMissingBlobHashes();
308
- if (missingAfterConsensus.length > 0 && this.archiveClient) {
284
+ if (getMissingBlobHashes().length > 0) {
285
+ await trySourceB();
286
+ }
287
+ if (getMissingBlobHashes().length > 0) {
288
+ throw new Error('Still missing blobs after trying all primary sources');
289
+ }
290
+ }, 'blob retrieval', makeBackoff(backoff), this.log, true);
291
+ return returnWithCallback(getFilledBlobs());
292
+ } catch {
293
+ // Exhausted retries, continue to archive fallback
294
+ }
295
+ // Archive fallback
296
+ const missingAfterPrimary = getMissingBlobHashes();
297
+ if (missingAfterPrimary.length > 0 && this.archiveClient) {
309
298
  const archiveCtx = {
310
299
  archiveUrl: this.archiveClient.getBaseUrl(),
311
300
  ...ctx
312
301
  };
313
- this.log.trace(`Attempting to get ${missingAfterConsensus.length} blobs from archive`, archiveCtx);
302
+ this.log.trace(`Attempting to get ${missingAfterPrimary.length} blobs from archive`, archiveCtx);
314
303
  const allBlobs = await this.archiveClient.getBlobsFromBlock(blockHash);
315
304
  if (!allBlobs) {
316
305
  this.log.debug('No blobs found from archive client', archiveCtx);
@@ -326,13 +315,63 @@ export class HttpBlobClient {
326
315
  const result = getFilledBlobs();
327
316
  if (result.length < blobHashes.length) {
328
317
  this.log.warn(`Failed to fetch all blobs for ${blockHash} from all blob sources (got ${result.length}/${blobHashes.length})`, {
329
- l1ConsensusHostUrls,
318
+ l1ConsensusHostUrls: this.config.l1ConsensusHostUrls,
330
319
  archiveUrl: this.archiveClient?.getBaseUrl(),
331
320
  fileStoreUrls: this.fileStoreClients.map((c)=>c.getBaseUrl())
332
321
  });
333
322
  }
334
323
  return returnWithCallback(result);
335
324
  }
325
+ /** Resolves the beacon slot number for the given block hash. Returns undefined if no consensus hosts. */ resolveSlotNumber(blockHash, opts) {
326
+ const { l1ConsensusHostUrls } = this.config;
327
+ if (!l1ConsensusHostUrls || l1ConsensusHostUrls.length === 0) {
328
+ return undefined;
329
+ }
330
+ // If no supernodes, no point resolving the slot
331
+ if (this.superNodeHostIndexes && this.superNodeHostIndexes.size === 0) {
332
+ return undefined;
333
+ }
334
+ return this.getSlotNumber(blockHash, opts?.parentBeaconBlockRoot, opts?.l1BlockTimestamp);
335
+ }
336
+ /**
337
+ * Try all supernode consensus hosts for blob sidecars.
338
+ * Skips hosts that were detected as non-supernodes during testSources().
339
+ */ async tryConsensusHosts(getSlotNumber, getMissingBlobHashes, fillResults, ctx) {
340
+ const { l1ConsensusHostUrls } = this.config;
341
+ if (!l1ConsensusHostUrls || l1ConsensusHostUrls.length === 0) {
342
+ return;
343
+ }
344
+ const slotNumber = await getSlotNumber();
345
+ if (!slotNumber) {
346
+ return;
347
+ }
348
+ for(let l1ConsensusHostIndex = 0; l1ConsensusHostIndex < l1ConsensusHostUrls.length; l1ConsensusHostIndex++){
349
+ const missingHashes = getMissingBlobHashes();
350
+ if (missingHashes.length === 0) {
351
+ break;
352
+ }
353
+ // Skip non-supernode hosts if we've already detected supernodes
354
+ if (this.superNodeHostIndexes && !this.superNodeHostIndexes.has(l1ConsensusHostIndex)) {
355
+ this.log.trace(`Skipping non-supernode consensus host`, {
356
+ l1ConsensusHostUrl: l1ConsensusHostUrls[l1ConsensusHostIndex]
357
+ });
358
+ continue;
359
+ }
360
+ const l1ConsensusHostUrl = l1ConsensusHostUrls[l1ConsensusHostIndex];
361
+ this.log.trace(`Attempting to get ${missingHashes.length} blobs from consensus host`, {
362
+ slotNumber,
363
+ l1ConsensusHostUrl,
364
+ ...ctx
365
+ });
366
+ const blobs = await this.getBlobsFromHost(l1ConsensusHostUrl, slotNumber, l1ConsensusHostIndex, missingHashes);
367
+ const result = await fillResults(blobs);
368
+ this.log.debug(`Got ${blobs.length} blobs from consensus host (total: ${result.length}/${ctx.blobHashes.length})`, {
369
+ slotNumber,
370
+ l1ConsensusHostUrl,
371
+ ...ctx
372
+ });
373
+ }
374
+ }
336
375
  /**
337
376
  * Try all filestores once (shuffled for load distribution).
338
377
  * @param getMissingBlobHashes - Function to get remaining blob hashes to fetch
@@ -419,19 +458,20 @@ export class HttpBlobClient {
419
458
  }
420
459
  baseUrl += `?${params.toString()}`;
421
460
  }
422
- const { url, ...options } = getBeaconNodeFetchOptions(baseUrl, this.config, l1ConsensusHostIndex);
461
+ const { url, logSafeUrl, ...options } = getBeaconNodeFetchOptions(baseUrl, this.config, l1ConsensusHostIndex);
423
462
  this.log.debug(`Fetching blob sidecar for ${blockHashOrSlot}`, {
424
- url,
463
+ url: logSafeUrl,
425
464
  ...options
426
465
  });
427
- return this.fetch(url, options);
466
+ // No retry here — this is called inside the main retry loop in getBlobSidecar
467
+ return fetch(url, options);
428
468
  }
429
469
  async getLatestSlotNumber(hostUrl, l1ConsensusHostIndex) {
430
470
  try {
431
471
  const baseUrl = `${hostUrl}/eth/v1/beacon/headers/head`;
432
- const { url, ...options } = getBeaconNodeFetchOptions(baseUrl, this.config, l1ConsensusHostIndex);
472
+ const { url, logSafeUrl, ...options } = getBeaconNodeFetchOptions(baseUrl, this.config, l1ConsensusHostIndex);
433
473
  this.log.debug(`Fetching latest slot number`, {
434
- url,
474
+ url: logSafeUrl,
435
475
  ...options
436
476
  });
437
477
  const res = await this.fetch(url, options);
@@ -640,11 +680,15 @@ function getBeaconNodeFetchOptions(url, config, l1ConsensusHostIndex) {
640
680
  const l1ConsensusHostApiKey = l1ConsensusHostIndex !== undefined && l1ConsensusHostApiKeys && l1ConsensusHostApiKeys[l1ConsensusHostIndex];
641
681
  const l1ConsensusHostApiKeyHeader = l1ConsensusHostIndex !== undefined && l1ConsensusHostApiKeyHeaders && l1ConsensusHostApiKeyHeaders[l1ConsensusHostIndex];
642
682
  let formattedUrl = url;
683
+ let logSafeUrl = url;
643
684
  if (l1ConsensusHostApiKey && l1ConsensusHostApiKey.getValue() !== '' && !l1ConsensusHostApiKeyHeader) {
644
- formattedUrl += `${formattedUrl.includes('?') ? '&' : '?'}key=${l1ConsensusHostApiKey.getValue()}`;
685
+ const separator = formattedUrl.includes('?') ? '&' : '?';
686
+ formattedUrl += `${separator}key=${l1ConsensusHostApiKey.getValue()}`;
687
+ logSafeUrl += `${separator}key=[REDACTED]`;
645
688
  }
646
689
  return {
647
690
  url: formattedUrl,
691
+ logSafeUrl,
648
692
  ...l1ConsensusHostApiKey && l1ConsensusHostApiKeyHeader && {
649
693
  headers: {
650
694
  [l1ConsensusHostApiKeyHeader]: l1ConsensusHostApiKey.getValue()
@@ -5,9 +5,7 @@ import type { Blob } from '@aztec/blob-lib';
5
5
  export interface GetBlobSidecarOptions {
6
6
  /**
7
7
  * True if the archiver is catching up (historical sync), false if near tip.
8
- * This affects source ordering:
9
- * - Historical: FileStore first (data should exist), then L1 consensus, then archive (eg. blobscan)
10
- * - Near tip: FileStore first with no retries (data should exist), L1 consensus second (freshest data), then FileStore with retries, then archive (eg. blobscan)
8
+ * Historical sync uses a shorter retry backoff since blobs should already exist.
11
9
  */
12
10
  isHistoricalSync?: boolean;
13
11
  /**
@@ -36,4 +34,4 @@ export interface BlobClientInterface {
36
34
  /** Returns true if this client can upload blobs to filestore. */
37
35
  canUpload(): boolean;
38
36
  }
39
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW50ZXJmYWNlLmQudHMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvY2xpZW50L2ludGVyZmFjZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssRUFBRSxJQUFJLEVBQUUsTUFBTSxpQkFBaUIsQ0FBQztBQUU1Qzs7R0FFRztBQUNILE1BQU0sV0FBVyxxQkFBcUI7SUFDcEM7Ozs7O09BS0c7SUFDSCxnQkFBZ0IsQ0FBQyxFQUFFLE9BQU8sQ0FBQztJQUMzQjs7O09BR0c7SUFDSCxxQkFBcUIsQ0FBQyxFQUFFLE1BQU0sQ0FBQztJQUMvQjs7OztPQUlHO0lBQ0gsZ0JBQWdCLENBQUMsRUFBRSxNQUFNLENBQUM7Q0FDM0I7QUFFRCxNQUFNLFdBQVcsbUJBQW1CO0lBQ2xDLDBFQUEwRTtJQUMxRSxvQkFBb0IsQ0FBQyxLQUFLLEVBQUUsSUFBSSxFQUFFLEdBQUcsT0FBTyxDQUFDLE9BQU8sQ0FBQyxDQUFDO0lBQ3RELHFFQUFxRTtJQUNyRSxjQUFjLENBQUMsT0FBTyxFQUFFLE1BQU0sRUFBRSxVQUFVLENBQUMsRUFBRSxNQUFNLEVBQUUsRUFBRSxJQUFJLENBQUMsRUFBRSxxQkFBcUIsR0FBRyxPQUFPLENBQUMsSUFBSSxFQUFFLENBQUMsQ0FBQztJQUN0Ryw2RUFBNkU7SUFDN0UsS0FBSyxDQUFDLElBQUksT0FBTyxDQUFDLElBQUksQ0FBQyxDQUFDO0lBQ3hCLG9GQUFvRjtJQUNwRixXQUFXLElBQUksT0FBTyxDQUFDLElBQUksQ0FBQyxDQUFDO0lBQzdCLDBEQUEwRDtJQUMxRCxJQUFJLENBQUMsSUFBSSxJQUFJLENBQUM7SUFDZCxpRUFBaUU7SUFDakUsU0FBUyxJQUFJLE9BQU8sQ0FBQztDQUN0QiJ9
37
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW50ZXJmYWNlLmQudHMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvY2xpZW50L2ludGVyZmFjZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssRUFBRSxJQUFJLEVBQUUsTUFBTSxpQkFBaUIsQ0FBQztBQUU1Qzs7R0FFRztBQUNILE1BQU0sV0FBVyxxQkFBcUI7SUFDcEM7OztPQUdHO0lBQ0gsZ0JBQWdCLENBQUMsRUFBRSxPQUFPLENBQUM7SUFDM0I7OztPQUdHO0lBQ0gscUJBQXFCLENBQUMsRUFBRSxNQUFNLENBQUM7SUFDL0I7Ozs7T0FJRztJQUNILGdCQUFnQixDQUFDLEVBQUUsTUFBTSxDQUFDO0NBQzNCO0FBRUQsTUFBTSxXQUFXLG1CQUFtQjtJQUNsQywwRUFBMEU7SUFDMUUsb0JBQW9CLENBQUMsS0FBSyxFQUFFLElBQUksRUFBRSxHQUFHLE9BQU8sQ0FBQyxPQUFPLENBQUMsQ0FBQztJQUN0RCxxRUFBcUU7SUFDckUsY0FBYyxDQUFDLE9BQU8sRUFBRSxNQUFNLEVBQUUsVUFBVSxDQUFDLEVBQUUsTUFBTSxFQUFFLEVBQUUsSUFBSSxDQUFDLEVBQUUscUJBQXFCLEdBQUcsT0FBTyxDQUFDLElBQUksRUFBRSxDQUFDLENBQUM7SUFDdEcsNkVBQTZFO0lBQzdFLEtBQUssQ0FBQyxJQUFJLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FBQztJQUN4QixvRkFBb0Y7SUFDcEYsV0FBVyxJQUFJLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FBQztJQUM3QiwwREFBMEQ7SUFDMUQsSUFBSSxDQUFDLElBQUksSUFBSSxDQUFDO0lBQ2QsaUVBQWlFO0lBQ2pFLFNBQVMsSUFBSSxPQUFPLENBQUM7Q0FDdEIifQ==
@@ -1 +1 @@
1
- {"version":3,"file":"interface.d.ts","sourceRoot":"","sources":["../../src/client/interface.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AAE5C;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B;;;OAGG;IACH,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,mBAAmB;IAClC,0EAA0E;IAC1E,oBAAoB,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACtD,qEAAqE;IACrE,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,EAAE,EAAE,IAAI,CAAC,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC;IACtG,6EAA6E;IAC7E,KAAK,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACxB,oFAAoF;IACpF,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7B,0DAA0D;IAC1D,IAAI,CAAC,IAAI,IAAI,CAAC;IACd,iEAAiE;IACjE,SAAS,IAAI,OAAO,CAAC;CACtB"}
1
+ {"version":3,"file":"interface.d.ts","sourceRoot":"","sources":["../../src/client/interface.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AAE5C;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC;;;OAGG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B;;;OAGG;IACH,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,mBAAmB;IAClC,0EAA0E;IAC1E,oBAAoB,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACtD,qEAAqE;IACrE,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,EAAE,EAAE,IAAI,CAAC,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC;IACtG,6EAA6E;IAC7E,KAAK,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACxB,oFAAoF;IACpF,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7B,0DAA0D;IAC1D,IAAI,CAAC,IAAI,IAAI,CAAC;IACd,iEAAiE;IACjE,SAAS,IAAI,OAAO,CAAC;CACtB"}
@@ -1,4 +1,5 @@
1
1
  import { type Logger } from '@aztec/foundation/log';
2
+ import { type HttpFileStoreOptions } from '@aztec/stdlib/file-store';
2
3
  import { FileStoreBlobClient } from './filestore_blob_client.js';
3
4
  /**
4
5
  * Metadata required to construct the base path for blob storage.
@@ -25,8 +26,8 @@ export declare function makeBlobBasePath(metadata: BlobFileStoreMetadata): strin
25
26
  * @param logger - Optional logger
26
27
  * @returns A FileStoreBlobClient for reading blobs, or undefined if storeUrl is undefined
27
28
  */
28
- export declare function createReadOnlyFileStoreBlobClient(storeUrl: string, metadata: BlobFileStoreMetadata, logger?: Logger): Promise<FileStoreBlobClient>;
29
- export declare function createReadOnlyFileStoreBlobClient(storeUrl: string | undefined, metadata: BlobFileStoreMetadata, logger?: Logger): Promise<FileStoreBlobClient | undefined>;
29
+ export declare function createReadOnlyFileStoreBlobClient(storeUrl: string, metadata: BlobFileStoreMetadata, logger?: Logger, httpOptions?: HttpFileStoreOptions): Promise<FileStoreBlobClient>;
30
+ export declare function createReadOnlyFileStoreBlobClient(storeUrl: string | undefined, metadata: BlobFileStoreMetadata, logger?: Logger, httpOptions?: HttpFileStoreOptions): Promise<FileStoreBlobClient | undefined>;
30
31
  /**
31
32
  * Creates multiple read-only FileStoreBlobClients from an array of URLs.
32
33
  *
@@ -35,7 +36,7 @@ export declare function createReadOnlyFileStoreBlobClient(storeUrl: string | und
35
36
  * @param logger - Optional logger
36
37
  * @returns Array of FileStoreBlobClients (excludes any that failed to create)
37
38
  */
38
- export declare function createReadOnlyFileStoreBlobClients(storeUrls: string[] | undefined, metadata: BlobFileStoreMetadata, logger?: Logger): Promise<FileStoreBlobClient[]>;
39
+ export declare function createReadOnlyFileStoreBlobClients(storeUrls: string[] | undefined, metadata: BlobFileStoreMetadata, logger?: Logger, httpOptions?: HttpFileStoreOptions): Promise<FileStoreBlobClient[]>;
39
40
  /**
40
41
  * Creates a writable FileStoreBlobClient for uploading blobs.
41
42
  * Note: https:// URLs are not supported for upload, only s3://, gs://, or file://.
@@ -47,4 +48,4 @@ export declare function createReadOnlyFileStoreBlobClients(storeUrls: string[] |
47
48
  */
48
49
  export declare function createWritableFileStoreBlobClient(storeUrl: string, metadata: BlobFileStoreMetadata, logger?: Logger): Promise<FileStoreBlobClient>;
49
50
  export declare function createWritableFileStoreBlobClient(storeUrl: string | undefined, metadata: BlobFileStoreMetadata, logger?: Logger): Promise<FileStoreBlobClient | undefined>;
50
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZmFjdG9yeS5kLnRzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2ZpbGVzdG9yZS9mYWN0b3J5LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxLQUFLLE1BQU0sRUFBZ0IsTUFBTSx1QkFBdUIsQ0FBQztBQVFsRSxPQUFPLEVBQUUsbUJBQW1CLEVBQUUsTUFBTSw0QkFBNEIsQ0FBQztBQUVqRTs7O0dBR0c7QUFDSCxNQUFNLFdBQVcscUJBQXFCO0lBQ3BDLHNCQUFzQjtJQUN0QixTQUFTLEVBQUUsTUFBTSxDQUFDO0lBQ2xCLHlCQUF5QjtJQUN6QixhQUFhLEVBQUUsTUFBTSxDQUFDO0lBQ3RCLDhEQUE4RDtJQUM5RCxhQUFhLEVBQUUsTUFBTSxDQUFDO0NBQ3ZCO0FBRUQ7OztHQUdHO0FBQ0gsd0JBQWdCLGdCQUFnQixDQUFDLFFBQVEsRUFBRSxxQkFBcUIsR0FBRyxNQUFNLENBS3hFO0FBRUQ7Ozs7Ozs7R0FPRztBQUNILHdCQUFzQixpQ0FBaUMsQ0FDckQsUUFBUSxFQUFFLE1BQU0sRUFDaEIsUUFBUSxFQUFFLHFCQUFxQixFQUMvQixNQUFNLENBQUMsRUFBRSxNQUFNLEdBQ2QsT0FBTyxDQUFDLG1CQUFtQixDQUFDLENBQUM7QUFDaEMsd0JBQXNCLGlDQUFpQyxDQUNyRCxRQUFRLEVBQUUsTUFBTSxHQUFHLFNBQVMsRUFDNUIsUUFBUSxFQUFFLHFCQUFxQixFQUMvQixNQUFNLENBQUMsRUFBRSxNQUFNLEdBQ2QsT0FBTyxDQUFDLG1CQUFtQixHQUFHLFNBQVMsQ0FBQyxDQUFDO0FBbUI1Qzs7Ozs7OztHQU9HO0FBQ0gsd0JBQXNCLGtDQUFrQyxDQUN0RCxTQUFTLEVBQUUsTUFBTSxFQUFFLEdBQUcsU0FBUyxFQUMvQixRQUFRLEVBQUUscUJBQXFCLEVBQy9CLE1BQU0sQ0FBQyxFQUFFLE1BQU0sR0FDZCxPQUFPLENBQUMsbUJBQW1CLEVBQUUsQ0FBQyxDQW9CaEM7QUFFRDs7Ozs7Ozs7R0FRRztBQUNILHdCQUFzQixpQ0FBaUMsQ0FDckQsUUFBUSxFQUFFLE1BQU0sRUFDaEIsUUFBUSxFQUFFLHFCQUFxQixFQUMvQixNQUFNLENBQUMsRUFBRSxNQUFNLEdBQ2QsT0FBTyxDQUFDLG1CQUFtQixDQUFDLENBQUM7QUFDaEMsd0JBQXNCLGlDQUFpQyxDQUNyRCxRQUFRLEVBQUUsTUFBTSxHQUFHLFNBQVMsRUFDNUIsUUFBUSxFQUFFLHFCQUFxQixFQUMvQixNQUFNLENBQUMsRUFBRSxNQUFNLEdBQ2QsT0FBTyxDQUFDLG1CQUFtQixHQUFHLFNBQVMsQ0FBQyxDQUFDIn0=
51
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZmFjdG9yeS5kLnRzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2ZpbGVzdG9yZS9mYWN0b3J5LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxLQUFLLE1BQU0sRUFBZ0IsTUFBTSx1QkFBdUIsQ0FBQztBQUNsRSxPQUFPLEVBRUwsS0FBSyxvQkFBb0IsRUFJMUIsTUFBTSwwQkFBMEIsQ0FBQztBQUVsQyxPQUFPLEVBQUUsbUJBQW1CLEVBQUUsTUFBTSw0QkFBNEIsQ0FBQztBQUVqRTs7O0dBR0c7QUFDSCxNQUFNLFdBQVcscUJBQXFCO0lBQ3BDLHNCQUFzQjtJQUN0QixTQUFTLEVBQUUsTUFBTSxDQUFDO0lBQ2xCLHlCQUF5QjtJQUN6QixhQUFhLEVBQUUsTUFBTSxDQUFDO0lBQ3RCLDhEQUE4RDtJQUM5RCxhQUFhLEVBQUUsTUFBTSxDQUFDO0NBQ3ZCO0FBRUQ7OztHQUdHO0FBQ0gsd0JBQWdCLGdCQUFnQixDQUFDLFFBQVEsRUFBRSxxQkFBcUIsR0FBRyxNQUFNLENBS3hFO0FBRUQ7Ozs7Ozs7R0FPRztBQUNILHdCQUFzQixpQ0FBaUMsQ0FDckQsUUFBUSxFQUFFLE1BQU0sRUFDaEIsUUFBUSxFQUFFLHFCQUFxQixFQUMvQixNQUFNLENBQUMsRUFBRSxNQUFNLEVBQ2YsV0FBVyxDQUFDLEVBQUUsb0JBQW9CLEdBQ2pDLE9BQU8sQ0FBQyxtQkFBbUIsQ0FBQyxDQUFDO0FBQ2hDLHdCQUFzQixpQ0FBaUMsQ0FDckQsUUFBUSxFQUFFLE1BQU0sR0FBRyxTQUFTLEVBQzVCLFFBQVEsRUFBRSxxQkFBcUIsRUFDL0IsTUFBTSxDQUFDLEVBQUUsTUFBTSxFQUNmLFdBQVcsQ0FBQyxFQUFFLG9CQUFvQixHQUNqQyxPQUFPLENBQUMsbUJBQW1CLEdBQUcsU0FBUyxDQUFDLENBQUM7QUFvQjVDOzs7Ozs7O0dBT0c7QUFDSCx3QkFBc0Isa0NBQWtDLENBQ3RELFNBQVMsRUFBRSxNQUFNLEVBQUUsR0FBRyxTQUFTLEVBQy9CLFFBQVEsRUFBRSxxQkFBcUIsRUFDL0IsTUFBTSxDQUFDLEVBQUUsTUFBTSxFQUNmLFdBQVcsQ0FBQyxFQUFFLG9CQUFvQixHQUNqQyxPQUFPLENBQUMsbUJBQW1CLEVBQUUsQ0FBQyxDQW9CaEM7QUFFRDs7Ozs7Ozs7R0FRRztBQUNILHdCQUFzQixpQ0FBaUMsQ0FDckQsUUFBUSxFQUFFLE1BQU0sRUFDaEIsUUFBUSxFQUFFLHFCQUFxQixFQUMvQixNQUFNLENBQUMsRUFBRSxNQUFNLEdBQ2QsT0FBTyxDQUFDLG1CQUFtQixDQUFDLENBQUM7QUFDaEMsd0JBQXNCLGlDQUFpQyxDQUNyRCxRQUFRLEVBQUUsTUFBTSxHQUFHLFNBQVMsRUFDNUIsUUFBUSxFQUFFLHFCQUFxQixFQUMvQixNQUFNLENBQUMsRUFBRSxNQUFNLEdBQ2QsT0FBTyxDQUFDLG1CQUFtQixHQUFHLFNBQVMsQ0FBQyxDQUFDIn0=
@@ -1 +1 @@
1
- {"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../../src/filestore/factory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,MAAM,EAAgB,MAAM,uBAAuB,CAAC;AAQlE,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAEjE;;;GAGG;AACH,MAAM,WAAW,qBAAqB;IACpC,sBAAsB;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,yBAAyB;IACzB,aAAa,EAAE,MAAM,CAAC;IACtB,8DAA8D;IAC9D,aAAa,EAAE,MAAM,CAAC;CACvB;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,qBAAqB,GAAG,MAAM,CAKxE;AAED;;;;;;;GAOG;AACH,wBAAsB,iCAAiC,CACrD,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,qBAAqB,EAC/B,MAAM,CAAC,EAAE,MAAM,GACd,OAAO,CAAC,mBAAmB,CAAC,CAAC;AAChC,wBAAsB,iCAAiC,CACrD,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,QAAQ,EAAE,qBAAqB,EAC/B,MAAM,CAAC,EAAE,MAAM,GACd,OAAO,CAAC,mBAAmB,GAAG,SAAS,CAAC,CAAC;AAmB5C;;;;;;;GAOG;AACH,wBAAsB,kCAAkC,CACtD,SAAS,EAAE,MAAM,EAAE,GAAG,SAAS,EAC/B,QAAQ,EAAE,qBAAqB,EAC/B,MAAM,CAAC,EAAE,MAAM,GACd,OAAO,CAAC,mBAAmB,EAAE,CAAC,CAoBhC;AAED;;;;;;;;GAQG;AACH,wBAAsB,iCAAiC,CACrD,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,qBAAqB,EAC/B,MAAM,CAAC,EAAE,MAAM,GACd,OAAO,CAAC,mBAAmB,CAAC,CAAC;AAChC,wBAAsB,iCAAiC,CACrD,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,QAAQ,EAAE,qBAAqB,EAC/B,MAAM,CAAC,EAAE,MAAM,GACd,OAAO,CAAC,mBAAmB,GAAG,SAAS,CAAC,CAAC"}
1
+ {"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../../src/filestore/factory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,MAAM,EAAgB,MAAM,uBAAuB,CAAC;AAClE,OAAO,EAEL,KAAK,oBAAoB,EAI1B,MAAM,0BAA0B,CAAC;AAElC,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAEjE;;;GAGG;AACH,MAAM,WAAW,qBAAqB;IACpC,sBAAsB;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,yBAAyB;IACzB,aAAa,EAAE,MAAM,CAAC;IACtB,8DAA8D;IAC9D,aAAa,EAAE,MAAM,CAAC;CACvB;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,qBAAqB,GAAG,MAAM,CAKxE;AAED;;;;;;;GAOG;AACH,wBAAsB,iCAAiC,CACrD,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,qBAAqB,EAC/B,MAAM,CAAC,EAAE,MAAM,EACf,WAAW,CAAC,EAAE,oBAAoB,GACjC,OAAO,CAAC,mBAAmB,CAAC,CAAC;AAChC,wBAAsB,iCAAiC,CACrD,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,QAAQ,EAAE,qBAAqB,EAC/B,MAAM,CAAC,EAAE,MAAM,EACf,WAAW,CAAC,EAAE,oBAAoB,GACjC,OAAO,CAAC,mBAAmB,GAAG,SAAS,CAAC,CAAC;AAoB5C;;;;;;;GAOG;AACH,wBAAsB,kCAAkC,CACtD,SAAS,EAAE,MAAM,EAAE,GAAG,SAAS,EAC/B,QAAQ,EAAE,qBAAqB,EAC/B,MAAM,CAAC,EAAE,MAAM,EACf,WAAW,CAAC,EAAE,oBAAoB,GACjC,OAAO,CAAC,mBAAmB,EAAE,CAAC,CAoBhC;AAED;;;;;;;;GAQG;AACH,wBAAsB,iCAAiC,CACrD,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,qBAAqB,EAC/B,MAAM,CAAC,EAAE,MAAM,GACd,OAAO,CAAC,mBAAmB,CAAC,CAAC;AAChC,wBAAsB,iCAAiC,CACrD,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,QAAQ,EAAE,qBAAqB,EAC/B,MAAM,CAAC,EAAE,MAAM,GACd,OAAO,CAAC,mBAAmB,GAAG,SAAS,CAAC,CAAC"}
@@ -10,7 +10,7 @@ import { FileStoreBlobClient } from './filestore_blob_client.js';
10
10
  const normalizedAddress = rollupAddress.toLowerCase().replace(/^0x/, '');
11
11
  return `aztec-${l1ChainId}-${rollupVersion}-0x${normalizedAddress}`;
12
12
  }
13
- export async function createReadOnlyFileStoreBlobClient(storeUrl, metadata, logger) {
13
+ export async function createReadOnlyFileStoreBlobClient(storeUrl, metadata, logger, httpOptions) {
14
14
  if (!storeUrl) {
15
15
  return undefined;
16
16
  }
@@ -20,7 +20,7 @@ export async function createReadOnlyFileStoreBlobClient(storeUrl, metadata, logg
20
20
  storeUrl,
21
21
  basePath
22
22
  });
23
- const store = await createReadOnlyFileStore(storeUrl, log);
23
+ const store = await createReadOnlyFileStore(storeUrl, log, httpOptions);
24
24
  return new FileStoreBlobClient(store, basePath, log);
25
25
  }
26
26
  /**
@@ -30,7 +30,7 @@ export async function createReadOnlyFileStoreBlobClient(storeUrl, metadata, logg
30
30
  * @param metadata - Chain metadata for constructing the base path
31
31
  * @param logger - Optional logger
32
32
  * @returns Array of FileStoreBlobClients (excludes any that failed to create)
33
- */ export async function createReadOnlyFileStoreBlobClients(storeUrls, metadata, logger) {
33
+ */ export async function createReadOnlyFileStoreBlobClients(storeUrls, metadata, logger, httpOptions) {
34
34
  if (!storeUrls || storeUrls.length === 0) {
35
35
  return [];
36
36
  }
@@ -38,7 +38,7 @@ export async function createReadOnlyFileStoreBlobClient(storeUrl, metadata, logg
38
38
  const clients = [];
39
39
  for (const storeUrl of storeUrls){
40
40
  try {
41
- const client = await createReadOnlyFileStoreBlobClient(storeUrl, metadata, log);
41
+ const client = await createReadOnlyFileStoreBlobClient(storeUrl, metadata, log, httpOptions);
42
42
  if (client) {
43
43
  clients.push(client);
44
44
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aztec/blob-client",
3
- "version": "0.0.1-commit.e0f15ab9b",
3
+ "version": "0.0.1-commit.e304674f1",
4
4
  "type": "module",
5
5
  "bin": "./dest/client/bin/index.js",
6
6
  "exports": {
@@ -56,12 +56,12 @@
56
56
  ]
57
57
  },
58
58
  "dependencies": {
59
- "@aztec/blob-lib": "0.0.1-commit.e0f15ab9b",
60
- "@aztec/ethereum": "0.0.1-commit.e0f15ab9b",
61
- "@aztec/foundation": "0.0.1-commit.e0f15ab9b",
62
- "@aztec/kv-store": "0.0.1-commit.e0f15ab9b",
63
- "@aztec/stdlib": "0.0.1-commit.e0f15ab9b",
64
- "@aztec/telemetry-client": "0.0.1-commit.e0f15ab9b",
59
+ "@aztec/blob-lib": "0.0.1-commit.e304674f1",
60
+ "@aztec/ethereum": "0.0.1-commit.e304674f1",
61
+ "@aztec/foundation": "0.0.1-commit.e304674f1",
62
+ "@aztec/kv-store": "0.0.1-commit.e304674f1",
63
+ "@aztec/stdlib": "0.0.1-commit.e304674f1",
64
+ "@aztec/telemetry-client": "0.0.1-commit.e304674f1",
65
65
  "express": "^4.21.2",
66
66
  "snappy": "^7.2.2",
67
67
  "source-map-support": "^0.5.21",
@@ -59,6 +59,12 @@ export interface BlobClientConfig extends BlobArchiveApiConfig {
59
59
 
60
60
  /** Timeout for HTTP requests to the L1 RPC node in ms. */
61
61
  l1HttpTimeoutMS?: number;
62
+
63
+ /** Whether to prefer filestores over consensus clients when fetching blobs. Default: false (consensus first). */
64
+ blobPreferFilestores?: boolean;
65
+
66
+ /** Timeout in ms for HTTP requests to the blob file store. Default: 10000 (10s). */
67
+ blobFileStoreTimeoutMs?: number;
62
68
  }
63
69
 
64
70
  export const blobClientConfigMapping: ConfigMappingsType<BlobClientConfig> = {
@@ -117,6 +123,16 @@ export const blobClientConfigMapping: ConfigMappingsType<BlobClientConfig> = {
117
123
  description: 'Timeout for HTTP requests to the L1 RPC node in ms.',
118
124
  ...optionalNumberConfigHelper(),
119
125
  },
126
+ blobPreferFilestores: {
127
+ env: 'BLOB_PREFER_FILESTORES',
128
+ description: 'Whether to prefer filestores over consensus clients when fetching blobs. Default: false.',
129
+ ...booleanConfigHelper(false),
130
+ },
131
+ blobFileStoreTimeoutMs: {
132
+ env: 'BLOB_FILE_STORE_TIMEOUT_MS',
133
+ description: 'Timeout in ms for HTTP requests to the blob file store. Default: 10000 (10s).',
134
+ ...optionalNumberConfigHelper(),
135
+ },
120
136
  ...blobArchiveApiConfigMappings,
121
137
  };
122
138
 
@@ -76,8 +76,15 @@ export async function createBlobClientWithFileStores(
76
76
  rollupAddress: config.l1Contracts.rollupAddress.toString(),
77
77
  };
78
78
 
79
+ // Disable internal retries for blob file stores — retry logic is handled by HttpBlobClient.
80
+ // Set a configurable timeout (default 10s) to avoid hanging on slow stores.
81
+ const httpOptions = {
82
+ retryBackoff: [] as number[],
83
+ timeoutMs: config.blobFileStoreTimeoutMs ?? 10_000,
84
+ };
85
+
79
86
  const [fileStoreClients, fileStoreUploadClient] = await Promise.all([
80
- createReadOnlyFileStoreBlobClients(config.blobFileStoreUrls, fileStoreMetadata, log),
87
+ createReadOnlyFileStoreBlobClients(config.blobFileStoreUrls, fileStoreMetadata, log, httpOptions),
81
88
  createWritableFileStoreBlobClient(config.blobFileStoreUploadUrl, fileStoreMetadata, log),
82
89
  ]);
83
90
 
@@ -30,6 +30,9 @@ export class HttpBlobClient implements BlobClientInterface {
30
30
  /** Cached beacon slot duration in seconds. Fetched once at startup. */
31
31
  private beaconSecondsPerSlot?: number;
32
32
 
33
+ /** Indexes of consensus hosts that serve blob sidecars (supernodes). Populated by testSources(). */
34
+ private superNodeHostIndexes?: Set<number>;
35
+
33
36
  constructor(
34
37
  config?: BlobClientConfig,
35
38
  private readonly opts: {
@@ -100,6 +103,8 @@ export class HttpBlobClient implements BlobClientInterface {
100
103
  let archiveSources = 0;
101
104
  let blobSinks = 0;
102
105
 
106
+ const detectedSuperNodes = new Set<number>();
107
+
103
108
  if (l1ConsensusHostUrls && l1ConsensusHostUrls.length > 0) {
104
109
  for (let l1ConsensusHostIndex = 0; l1ConsensusHostIndex < l1ConsensusHostUrls.length; l1ConsensusHostIndex++) {
105
110
  const l1ConsensusHostUrl = l1ConsensusHostUrls[l1ConsensusHostIndex];
@@ -134,9 +139,12 @@ export class HttpBlobClient implements BlobClientInterface {
134
139
  const blobRes = await this.fetch(blobUrl, blobOptions);
135
140
  if (blobRes.ok) {
136
141
  this.log.info(`L1 consensus host serves blob sidecars (supernode)`, { l1ConsensusHostUrl });
142
+ detectedSuperNodes.add(l1ConsensusHostIndex);
137
143
  consensusSuperNodes++;
138
144
  } else {
139
- this.log.info(`L1 consensus host does not serve blob sidecars`, { l1ConsensusHostUrl });
145
+ this.log.info(`L1 consensus host does not serve blob sidecars, skipping for blob fetching`, {
146
+ l1ConsensusHostUrl,
147
+ });
140
148
  consensusNonSuperNodes++;
141
149
  }
142
150
  } else {
@@ -149,6 +157,8 @@ export class HttpBlobClient implements BlobClientInterface {
149
157
  }
150
158
  }
151
159
 
160
+ this.superNodeHostIndexes = detectedSuperNodes;
161
+
152
162
  if (this.archiveClient) {
153
163
  try {
154
164
  const latest = await this.archiveClient.getLatestBlock();
@@ -218,18 +228,15 @@ export class HttpBlobClient implements BlobClientInterface {
218
228
  }
219
229
 
220
230
  /**
221
- * Get the blob sidecar
231
+ * Get the blob sidecar.
222
232
  *
223
- * If requesting from the blob client, we send the blobkHash
224
- * If requesting from the beacon node, we send the slot number
225
- *
226
- * Source ordering depends on sync state:
227
- * - Historical sync: blob client → FileStore → L1 consensus → Archive
228
- * - Near tip sync: blob client → FileStore → L1 consensus → FileStore (with retries) → Archive (eg blobscan)
233
+ * Alternates between two primary sources (consensus and filestore) in a retry loop,
234
+ * then falls back to archive if blobs are still missing. The order of the primary
235
+ * sources is configurable via `blobPreferFilestores`.
229
236
  *
230
237
  * @param blockHash - The block hash
231
238
  * @param blobHashes - The blob hashes to fetch
232
- * @param opts - Options including isHistoricalSync flag
239
+ * @param opts - Options for slot resolution
233
240
  * @returns The blobs
234
241
  */
235
242
  public async getBlobSidecar(
@@ -242,12 +249,11 @@ export class HttpBlobClient implements BlobClientInterface {
242
249
  return [];
243
250
  }
244
251
 
245
- const isHistoricalSync = opts?.isHistoricalSync ?? false;
246
252
  // Accumulate blobs across sources, preserving order and handling duplicates
247
253
  // resultBlobs[i] will contain the blob for blobHashes[i], or undefined if not yet found
248
254
  const resultBlobs: (Blob | undefined)[] = new Array(blobHashes.length).fill(undefined);
249
255
 
250
- // Helper to get missing blob hashes that we still need to fetch
256
+ // Helper to get missing blob hashes that we still need to fetch
251
257
  const getMissingBlobHashes = (): Buffer[] =>
252
258
  blobHashes
253
259
  .map((bh, i) => (resultBlobs[i] === undefined ? bh : undefined))
@@ -276,84 +282,60 @@ export class HttpBlobClient implements BlobClientInterface {
276
282
  return blobs;
277
283
  };
278
284
 
279
- const { l1ConsensusHostUrls } = this.config;
280
-
281
285
  const ctx = { blockHash, blobHashes: blobHashes.map(bufferToHex) };
282
286
 
283
- // Try filestore (quick, no retries) - useful for both historical and near-tip sync
284
- if (this.fileStoreClients.length > 0 && getMissingBlobHashes().length > 0) {
285
- await this.tryFileStores(getMissingBlobHashes, fillResults, ctx);
286
- if (getMissingBlobHashes().length === 0) {
287
- return returnWithCallback(getFilledBlobs());
287
+ // Lazily resolve the slot number only resolved when consensus hosts are actually tried.
288
+ let slotNumber: number | undefined;
289
+ let slotResolved = false;
290
+ const getSlotNumber = async (): Promise<number | undefined> => {
291
+ if (!slotResolved) {
292
+ slotNumber = await this.resolveSlotNumber(blockHash, opts);
293
+ slotResolved = true;
288
294
  }
289
- }
295
+ return slotNumber;
296
+ };
290
297
 
291
- const missingAfterSink = getMissingBlobHashes();
292
- if (missingAfterSink.length > 0 && l1ConsensusHostUrls && l1ConsensusHostUrls.length > 0) {
293
- // The beacon api can query by slot number, so we get that first
294
- const consensusCtx = { l1ConsensusHostUrls, ...ctx };
295
- this.log.trace(`Attempting to get slot number for block hash`, consensusCtx);
296
- const slotNumber = await this.getSlotNumber(blockHash, opts?.parentBeaconBlockRoot, opts?.l1BlockTimestamp);
297
- this.log.debug(`Got slot number ${slotNumber} from consensus host for querying blobs`, consensusCtx);
298
-
299
- if (slotNumber) {
300
- let l1ConsensusHostUrl: string;
301
- for (let l1ConsensusHostIndex = 0; l1ConsensusHostIndex < l1ConsensusHostUrls.length; l1ConsensusHostIndex++) {
302
- const missingHashes = getMissingBlobHashes();
303
- if (missingHashes.length === 0) {
304
- break;
305
- }
298
+ // Build the two source-try functions. The order depends on the config.
299
+ const tryConsensus = () => this.tryConsensusHosts(getSlotNumber, getMissingBlobHashes, fillResults, ctx);
300
+ const tryFilestores = () => this.tryFileStores(getMissingBlobHashes, fillResults, ctx);
306
301
 
307
- l1ConsensusHostUrl = l1ConsensusHostUrls[l1ConsensusHostIndex];
308
- this.log.trace(`Attempting to get ${missingHashes.length} blobs from consensus host`, {
309
- slotNumber,
310
- l1ConsensusHostUrl,
311
- ...ctx,
312
- });
313
- const blobs = await this.getBlobsFromHost(
314
- l1ConsensusHostUrl,
315
- slotNumber,
316
- l1ConsensusHostIndex,
317
- getMissingBlobHashes(),
318
- );
319
- const result = await fillResults(blobs);
320
- this.log.debug(
321
- `Got ${blobs.length} blobs from consensus host (total: ${result.length}/${blobHashes.length})`,
322
- { slotNumber, l1ConsensusHostUrl, ...ctx },
323
- );
324
- if (result.length === blobHashes.length) {
325
- return returnWithCallback(result);
326
- }
327
- }
328
- }
329
- }
302
+ const preferFilestores = this.config.blobPreferFilestores ?? false;
303
+ const [trySourceA, trySourceB] = preferFilestores ? [tryFilestores, tryConsensus] : [tryConsensus, tryFilestores];
330
304
 
331
- // For near-tip sync, retry filestores with backoff (eventual consistency)
332
- // This handles the case where blobs are still being uploaded by other validators
333
- if (!isHistoricalSync && this.fileStoreClients.length > 0 && getMissingBlobHashes().length > 0) {
334
- try {
335
- await retry(
336
- async () => {
337
- await this.tryFileStores(getMissingBlobHashes, fillResults, ctx);
338
- if (getMissingBlobHashes().length > 0) {
339
- throw new Error('Still missing blobs from filestores');
340
- }
341
- },
342
- 'filestore blob retrieval',
343
- makeBackoff([1, 1, 2]),
344
- this.log,
345
- true, // failSilently - expected to fail during eventual consistency
346
- );
347
- return returnWithCallback(getFilledBlobs());
348
- } catch {
349
- // Exhausted retries, continue to archive fallback
350
- }
305
+ // Historical sync: blobs should already exist, use shorter backoff for transient errors.
306
+ // Near-tip sync: blobs may still be uploading, use longer backoff for eventual consistency.
307
+ const isHistoricalSync = opts?.isHistoricalSync ?? false;
308
+ const backoff = isHistoricalSync ? [1, 1] : [1, 1, 1, 2, 2];
309
+
310
+ // Retry loop: alternate between the two primary sources with backoff.
311
+ try {
312
+ await retry(
313
+ async () => {
314
+ if (getMissingBlobHashes().length > 0) {
315
+ await trySourceA();
316
+ }
317
+ if (getMissingBlobHashes().length > 0) {
318
+ await trySourceB();
319
+ }
320
+ if (getMissingBlobHashes().length > 0) {
321
+ throw new Error('Still missing blobs after trying all primary sources');
322
+ }
323
+ },
324
+ 'blob retrieval',
325
+ makeBackoff(backoff),
326
+ this.log,
327
+ true, // failSilently — expected during eventual consistency
328
+ );
329
+ return returnWithCallback(getFilledBlobs());
330
+ } catch {
331
+ // Exhausted retries, continue to archive fallback
351
332
  }
352
333
 
353
- const missingAfterConsensus = getMissingBlobHashes();
354
- if (missingAfterConsensus.length > 0 && this.archiveClient) {
334
+ // Archive fallback
335
+ const missingAfterPrimary = getMissingBlobHashes();
336
+ if (missingAfterPrimary.length > 0 && this.archiveClient) {
355
337
  const archiveCtx = { archiveUrl: this.archiveClient.getBaseUrl(), ...ctx };
356
- this.log.trace(`Attempting to get ${missingAfterConsensus.length} blobs from archive`, archiveCtx);
338
+ this.log.trace(`Attempting to get ${missingAfterPrimary.length} blobs from archive`, archiveCtx);
357
339
  const allBlobs = await this.archiveClient.getBlobsFromBlock(blockHash);
358
340
  if (!allBlobs) {
359
341
  this.log.debug('No blobs found from archive client', archiveCtx);
@@ -375,7 +357,7 @@ export class HttpBlobClient implements BlobClientInterface {
375
357
  this.log.warn(
376
358
  `Failed to fetch all blobs for ${blockHash} from all blob sources (got ${result.length}/${blobHashes.length})`,
377
359
  {
378
- l1ConsensusHostUrls,
360
+ l1ConsensusHostUrls: this.config.l1ConsensusHostUrls,
379
361
  archiveUrl: this.archiveClient?.getBaseUrl(),
380
362
  fileStoreUrls: this.fileStoreClients.map(c => c.getBaseUrl()),
381
363
  },
@@ -384,6 +366,71 @@ export class HttpBlobClient implements BlobClientInterface {
384
366
  return returnWithCallback(result);
385
367
  }
386
368
 
369
+ /** Resolves the beacon slot number for the given block hash. Returns undefined if no consensus hosts. */
370
+ private resolveSlotNumber(
371
+ blockHash: `0x${string}`,
372
+ opts?: GetBlobSidecarOptions,
373
+ ): Promise<number | undefined> | undefined {
374
+ const { l1ConsensusHostUrls } = this.config;
375
+ if (!l1ConsensusHostUrls || l1ConsensusHostUrls.length === 0) {
376
+ return undefined;
377
+ }
378
+ // If no supernodes, no point resolving the slot
379
+ if (this.superNodeHostIndexes && this.superNodeHostIndexes.size === 0) {
380
+ return undefined;
381
+ }
382
+ return this.getSlotNumber(blockHash, opts?.parentBeaconBlockRoot, opts?.l1BlockTimestamp);
383
+ }
384
+
385
+ /**
386
+ * Try all supernode consensus hosts for blob sidecars.
387
+ * Skips hosts that were detected as non-supernodes during testSources().
388
+ */
389
+ private async tryConsensusHosts(
390
+ getSlotNumber: () => Promise<number | undefined>,
391
+ getMissingBlobHashes: () => Buffer[],
392
+ fillResults: (blobs: BlobJson[]) => Promise<Blob[]>,
393
+ ctx: { blockHash: string; blobHashes: string[] },
394
+ ): Promise<void> {
395
+ const { l1ConsensusHostUrls } = this.config;
396
+ if (!l1ConsensusHostUrls || l1ConsensusHostUrls.length === 0) {
397
+ return;
398
+ }
399
+
400
+ const slotNumber = await getSlotNumber();
401
+ if (!slotNumber) {
402
+ return;
403
+ }
404
+
405
+ for (let l1ConsensusHostIndex = 0; l1ConsensusHostIndex < l1ConsensusHostUrls.length; l1ConsensusHostIndex++) {
406
+ const missingHashes = getMissingBlobHashes();
407
+ if (missingHashes.length === 0) {
408
+ break;
409
+ }
410
+
411
+ // Skip non-supernode hosts if we've already detected supernodes
412
+ if (this.superNodeHostIndexes && !this.superNodeHostIndexes.has(l1ConsensusHostIndex)) {
413
+ this.log.trace(`Skipping non-supernode consensus host`, {
414
+ l1ConsensusHostUrl: l1ConsensusHostUrls[l1ConsensusHostIndex],
415
+ });
416
+ continue;
417
+ }
418
+
419
+ const l1ConsensusHostUrl = l1ConsensusHostUrls[l1ConsensusHostIndex];
420
+ this.log.trace(`Attempting to get ${missingHashes.length} blobs from consensus host`, {
421
+ slotNumber,
422
+ l1ConsensusHostUrl,
423
+ ...ctx,
424
+ });
425
+ const blobs = await this.getBlobsFromHost(l1ConsensusHostUrl, slotNumber, l1ConsensusHostIndex, missingHashes);
426
+ const result = await fillResults(blobs);
427
+ this.log.debug(
428
+ `Got ${blobs.length} blobs from consensus host (total: ${result.length}/${ctx.blobHashes.length})`,
429
+ { slotNumber, l1ConsensusHostUrl, ...ctx },
430
+ );
431
+ }
432
+ }
433
+
387
434
  /**
388
435
  * Try all filestores once (shuffled for load distribution).
389
436
  * @param getMissingBlobHashes - Function to get remaining blob hashes to fetch
@@ -499,16 +546,17 @@ export class HttpBlobClient implements BlobClientInterface {
499
546
  baseUrl += `?${params.toString()}`;
500
547
  }
501
548
 
502
- const { url, ...options } = getBeaconNodeFetchOptions(baseUrl, this.config, l1ConsensusHostIndex);
503
- this.log.debug(`Fetching blob sidecar for ${blockHashOrSlot}`, { url, ...options });
504
- return this.fetch(url, options);
549
+ const { url, logSafeUrl, ...options } = getBeaconNodeFetchOptions(baseUrl, this.config, l1ConsensusHostIndex);
550
+ this.log.debug(`Fetching blob sidecar for ${blockHashOrSlot}`, { url: logSafeUrl, ...options });
551
+ // No retry here — this is called inside the main retry loop in getBlobSidecar
552
+ return fetch(url, options);
505
553
  }
506
554
 
507
555
  private async getLatestSlotNumber(hostUrl: string, l1ConsensusHostIndex?: number): Promise<number | undefined> {
508
556
  try {
509
557
  const baseUrl = `${hostUrl}/eth/v1/beacon/headers/head`;
510
- const { url, ...options } = getBeaconNodeFetchOptions(baseUrl, this.config, l1ConsensusHostIndex);
511
- this.log.debug(`Fetching latest slot number`, { url, ...options });
558
+ const { url, logSafeUrl, ...options } = getBeaconNodeFetchOptions(baseUrl, this.config, l1ConsensusHostIndex);
559
+ this.log.debug(`Fetching latest slot number`, { url: logSafeUrl, ...options });
512
560
  const res = await this.fetch(url, options);
513
561
  if (res.ok) {
514
562
  const body = await res.json();
@@ -771,12 +819,16 @@ function getBeaconNodeFetchOptions(url: string, config: BlobClientConfig, l1Cons
771
819
  l1ConsensusHostApiKeyHeaders[l1ConsensusHostIndex];
772
820
 
773
821
  let formattedUrl = url;
822
+ let logSafeUrl = url;
774
823
  if (l1ConsensusHostApiKey && l1ConsensusHostApiKey.getValue() !== '' && !l1ConsensusHostApiKeyHeader) {
775
- formattedUrl += `${formattedUrl.includes('?') ? '&' : '?'}key=${l1ConsensusHostApiKey.getValue()}`;
824
+ const separator = formattedUrl.includes('?') ? '&' : '?';
825
+ formattedUrl += `${separator}key=${l1ConsensusHostApiKey.getValue()}`;
826
+ logSafeUrl += `${separator}key=[REDACTED]`;
776
827
  }
777
828
 
778
829
  return {
779
830
  url: formattedUrl,
831
+ logSafeUrl,
780
832
  ...(l1ConsensusHostApiKey &&
781
833
  l1ConsensusHostApiKeyHeader && {
782
834
  headers: {
@@ -6,9 +6,7 @@ import type { Blob } from '@aztec/blob-lib';
6
6
  export interface GetBlobSidecarOptions {
7
7
  /**
8
8
  * True if the archiver is catching up (historical sync), false if near tip.
9
- * This affects source ordering:
10
- * - Historical: FileStore first (data should exist), then L1 consensus, then archive (eg. blobscan)
11
- * - Near tip: FileStore first with no retries (data should exist), L1 consensus second (freshest data), then FileStore with retries, then archive (eg. blobscan)
9
+ * Historical sync uses a shorter retry backoff since blobs should already exist.
12
10
  */
13
11
  isHistoricalSync?: boolean;
14
12
  /**
@@ -1,6 +1,7 @@
1
1
  import { type Logger, createLogger } from '@aztec/foundation/log';
2
2
  import {
3
3
  type FileStore,
4
+ type HttpFileStoreOptions,
4
5
  type ReadOnlyFileStore,
5
6
  createFileStore,
6
7
  createReadOnlyFileStore,
@@ -44,16 +45,19 @@ export async function createReadOnlyFileStoreBlobClient(
44
45
  storeUrl: string,
45
46
  metadata: BlobFileStoreMetadata,
46
47
  logger?: Logger,
48
+ httpOptions?: HttpFileStoreOptions,
47
49
  ): Promise<FileStoreBlobClient>;
48
50
  export async function createReadOnlyFileStoreBlobClient(
49
51
  storeUrl: string | undefined,
50
52
  metadata: BlobFileStoreMetadata,
51
53
  logger?: Logger,
54
+ httpOptions?: HttpFileStoreOptions,
52
55
  ): Promise<FileStoreBlobClient | undefined>;
53
56
  export async function createReadOnlyFileStoreBlobClient(
54
57
  storeUrl: string | undefined,
55
58
  metadata: BlobFileStoreMetadata,
56
59
  logger?: Logger,
60
+ httpOptions?: HttpFileStoreOptions,
57
61
  ): Promise<FileStoreBlobClient | undefined> {
58
62
  if (!storeUrl) {
59
63
  return undefined;
@@ -64,7 +68,7 @@ export async function createReadOnlyFileStoreBlobClient(
64
68
 
65
69
  log.debug(`Creating read-only filestore blob client`, { storeUrl, basePath });
66
70
 
67
- const store: ReadOnlyFileStore = await createReadOnlyFileStore(storeUrl, log);
71
+ const store: ReadOnlyFileStore = await createReadOnlyFileStore(storeUrl, log, httpOptions);
68
72
  return new FileStoreBlobClient(store, basePath, log);
69
73
  }
70
74
 
@@ -80,6 +84,7 @@ export async function createReadOnlyFileStoreBlobClients(
80
84
  storeUrls: string[] | undefined,
81
85
  metadata: BlobFileStoreMetadata,
82
86
  logger?: Logger,
87
+ httpOptions?: HttpFileStoreOptions,
83
88
  ): Promise<FileStoreBlobClient[]> {
84
89
  if (!storeUrls || storeUrls.length === 0) {
85
90
  return [];
@@ -90,7 +95,7 @@ export async function createReadOnlyFileStoreBlobClients(
90
95
 
91
96
  for (const storeUrl of storeUrls) {
92
97
  try {
93
- const client = await createReadOnlyFileStoreBlobClient(storeUrl, metadata, log);
98
+ const client = await createReadOnlyFileStoreBlobClient(storeUrl, metadata, log, httpOptions);
94
99
  if (client) {
95
100
  clients.push(client);
96
101
  }