@aztec/blob-client 0.0.1-commit.8afd444 → 0.0.1-commit.8c0b8ff

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.
@@ -12,7 +12,7 @@ export function describeBlobStore(getBlobStore) {
12
12
  Fr.random(),
13
13
  Fr.random()
14
14
  ];
15
- const blob = Blob.fromFields(testFields);
15
+ const blob = await Blob.fromFields(testFields);
16
16
  const blobHash = blob.getEthVersionedBlobHash();
17
17
  // Store the blob
18
18
  await blobStore.addBlobs([
@@ -28,11 +28,11 @@ export function describeBlobStore(getBlobStore) {
28
28
  });
29
29
  it('should handle multiple blobs stored and retrieved by their hashes', async ()=>{
30
30
  // Create two different blobs
31
- const blob1 = Blob.fromFields([
31
+ const blob1 = await Blob.fromFields([
32
32
  Fr.random(),
33
33
  Fr.random()
34
34
  ]);
35
- const blob2 = Blob.fromFields([
35
+ const blob2 = await Blob.fromFields([
36
36
  Fr.random(),
37
37
  Fr.random(),
38
38
  Fr.random()
@@ -64,13 +64,13 @@ export function describeBlobStore(getBlobStore) {
64
64
  });
65
65
  it('should handle retrieving subset of stored blobs', async ()=>{
66
66
  // Store multiple blobs
67
- const blob1 = Blob.fromFields([
67
+ const blob1 = await Blob.fromFields([
68
68
  Fr.random()
69
69
  ]);
70
- const blob2 = Blob.fromFields([
70
+ const blob2 = await Blob.fromFields([
71
71
  Fr.random()
72
72
  ]);
73
- const blob3 = Blob.fromFields([
73
+ const blob3 = await Blob.fromFields([
74
74
  Fr.random()
75
75
  ]);
76
76
  await blobStore.addBlobs([
@@ -90,7 +90,7 @@ export function describeBlobStore(getBlobStore) {
90
90
  expect(retrievedBlobs[1]).toEqual(blob3);
91
91
  });
92
92
  it('should handle duplicate blob hashes in request', async ()=>{
93
- const blob = Blob.fromFields([
93
+ const blob = await Blob.fromFields([
94
94
  Fr.random()
95
95
  ]);
96
96
  const blobHash = blob.getEthVersionedBlobHash();
@@ -112,8 +112,8 @@ export function describeBlobStore(getBlobStore) {
112
112
  Fr.random(),
113
113
  Fr.random()
114
114
  ];
115
- const blob1 = Blob.fromFields(fields);
116
- const blob2 = Blob.fromFields(fields);
115
+ const blob1 = await Blob.fromFields(fields);
116
+ const blob2 = await Blob.fromFields(fields);
117
117
  const blobHash = blob1.getEthVersionedBlobHash();
118
118
  // Store first blob
119
119
  await blobStore.addBlobs([
@@ -40,6 +40,8 @@ export interface BlobClientConfig extends BlobArchiveApiConfig {
40
40
  * Interval in minutes for uploading healthcheck file to file store (default: 60 = 1 hour)
41
41
  */
42
42
  blobHealthcheckUploadIntervalMinutes?: number;
43
+ /** Timeout for HTTP requests to the L1 RPC node in ms. */
44
+ l1HttpTimeoutMS?: number;
43
45
  }
44
46
  export declare const blobClientConfigMapping: ConfigMappingsType<BlobClientConfig>;
45
47
  /**
@@ -51,4 +53,4 @@ export declare function getBlobClientConfigFromEnv(): BlobClientConfig;
51
53
  * Returns whether the given blob client config has any remote sources defined.
52
54
  */
53
55
  export declare function hasRemoteBlobSources(config?: BlobClientConfig): boolean;
54
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29uZmlnLmQudHMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvY2xpZW50L2NvbmZpZy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQ0wsS0FBSyxrQkFBa0IsRUFDdkIsV0FBVyxFQUdaLE1BQU0sMEJBQTBCLENBQUM7QUFFbEMsT0FBTyxFQUFFLEtBQUssb0JBQW9CLEVBQWdDLE1BQU0sc0JBQXNCLENBQUM7QUFFL0Y7O0dBRUc7QUFDSCxNQUFNLFdBQVcsZ0JBQWlCLFNBQVEsb0JBQW9CO0lBQzVEOztPQUVHO0lBQ0gsU0FBUyxDQUFDLEVBQUUsTUFBTSxFQUFFLENBQUM7SUFFckI7O09BRUc7SUFDSCxtQkFBbUIsQ0FBQyxFQUFFLE1BQU0sRUFBRSxDQUFDO0lBRS9COztPQUVHO0lBQ0gsc0JBQXNCLENBQUMsRUFBRSxXQUFXLENBQUMsTUFBTSxDQUFDLEVBQUUsQ0FBQztJQUUvQzs7T0FFRztJQUNILDRCQUE0QixDQUFDLEVBQUUsTUFBTSxFQUFFLENBQUM7SUFFeEM7O09BRUc7SUFDSCxpQkFBaUIsQ0FBQyxFQUFFLE1BQU0sQ0FBQztJQUUzQjs7T0FFRztJQUNILHFCQUFxQixDQUFDLEVBQUUsT0FBTyxDQUFDO0lBRWhDOztPQUVHO0lBQ0gsaUJBQWlCLENBQUMsRUFBRSxNQUFNLEVBQUUsQ0FBQztJQUU3Qjs7T0FFRztJQUNILHNCQUFzQixDQUFDLEVBQUUsTUFBTSxDQUFDO0lBRWhDOztPQUVHO0lBQ0gsb0NBQW9DLENBQUMsRUFBRSxNQUFNLENBQUM7Q0FDL0M7QUFFRCxlQUFPLE1BQU0sdUJBQXVCLEVBQUUsa0JBQWtCLENBQUMsZ0JBQWdCLENBb0R4RSxDQUFDO0FBRUY7OztHQUdHO0FBQ0gsd0JBQWdCLDBCQUEwQixJQUFJLGdCQUFnQixDQUU3RDtBQUVEOztHQUVHO0FBQ0gsd0JBQWdCLG9CQUFvQixDQUFDLE1BQU0sR0FBRSxnQkFBcUIsR0FBRyxPQUFPLENBRTNFIn0=
56
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29uZmlnLmQudHMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvY2xpZW50L2NvbmZpZy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQ0wsS0FBSyxrQkFBa0IsRUFDdkIsV0FBVyxFQUlaLE1BQU0sMEJBQTBCLENBQUM7QUFFbEMsT0FBTyxFQUFFLEtBQUssb0JBQW9CLEVBQWdDLE1BQU0sc0JBQXNCLENBQUM7QUFFL0Y7O0dBRUc7QUFDSCxNQUFNLFdBQVcsZ0JBQWlCLFNBQVEsb0JBQW9CO0lBQzVEOztPQUVHO0lBQ0gsU0FBUyxDQUFDLEVBQUUsTUFBTSxFQUFFLENBQUM7SUFFckI7O09BRUc7SUFDSCxtQkFBbUIsQ0FBQyxFQUFFLE1BQU0sRUFBRSxDQUFDO0lBRS9COztPQUVHO0lBQ0gsc0JBQXNCLENBQUMsRUFBRSxXQUFXLENBQUMsTUFBTSxDQUFDLEVBQUUsQ0FBQztJQUUvQzs7T0FFRztJQUNILDRCQUE0QixDQUFDLEVBQUUsTUFBTSxFQUFFLENBQUM7SUFFeEM7O09BRUc7SUFDSCxpQkFBaUIsQ0FBQyxFQUFFLE1BQU0sQ0FBQztJQUUzQjs7T0FFRztJQUNILHFCQUFxQixDQUFDLEVBQUUsT0FBTyxDQUFDO0lBRWhDOztPQUVHO0lBQ0gsaUJBQWlCLENBQUMsRUFBRSxNQUFNLEVBQUUsQ0FBQztJQUU3Qjs7T0FFRztJQUNILHNCQUFzQixDQUFDLEVBQUUsTUFBTSxDQUFDO0lBRWhDOztPQUVHO0lBQ0gsb0NBQW9DLENBQUMsRUFBRSxNQUFNLENBQUM7SUFFOUMsMERBQTBEO0lBQzFELGVBQWUsQ0FBQyxFQUFFLE1BQU0sQ0FBQztDQUMxQjtBQUVELGVBQU8sTUFBTSx1QkFBdUIsRUFBRSxrQkFBa0IsQ0FBQyxnQkFBZ0IsQ0F5RHhFLENBQUM7QUFFRjs7O0dBR0c7QUFDSCx3QkFBZ0IsMEJBQTBCLElBQUksZ0JBQWdCLENBRTdEO0FBRUQ7O0dBRUc7QUFDSCx3QkFBZ0Isb0JBQW9CLENBQUMsTUFBTSxHQUFFLGdCQUFxQixHQUFHLE9BQU8sQ0FFM0UifQ==
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/client/config.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,kBAAkB,EACvB,WAAW,EAGZ,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;CAC/C;AAED,eAAO,MAAM,uBAAuB,EAAE,kBAAkB,CAAC,gBAAgB,CAoDxE,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;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,4 +1,4 @@
1
- import { SecretValue, booleanConfigHelper, getConfigFromMappings } from '@aztec/foundation/config';
1
+ import { SecretValue, booleanConfigHelper, getConfigFromMappings, optionalNumberConfigHelper } from '@aztec/foundation/config';
2
2
  import { blobArchiveApiConfigMappings } from '../archive/config.js';
3
3
  export const blobClientConfigMapping = {
4
4
  l1RpcUrls: {
@@ -45,6 +45,11 @@ export const blobClientConfigMapping = {
45
45
  description: 'Interval in minutes for uploading healthcheck file to file store (default: 60 = 1 hour)',
46
46
  parseEnv: (val)=>val ? +val : undefined
47
47
  },
48
+ l1HttpTimeoutMS: {
49
+ env: 'ETHEREUM_HTTP_TIMEOUT_MS',
50
+ description: 'Timeout for HTTP requests to the L1 RPC node in ms.',
51
+ ...optionalNumberConfigHelper()
52
+ },
48
53
  ...blobArchiveApiConfigMappings
49
54
  };
50
55
  /**
@@ -76,4 +76,4 @@ export declare class HttpBlobClient implements BlobClientInterface {
76
76
  */
77
77
  stop(): void;
78
78
  }
79
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaHR0cC5kLnRzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2NsaWVudC9odHRwLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxJQUFJLEVBQUUsS0FBSyxRQUFRLEVBQStCLE1BQU0saUJBQWlCLENBQUM7QUFFbkYsT0FBTyxFQUFFLEtBQUssTUFBTSxFQUFnQixNQUFNLHVCQUF1QixDQUFDO0FBT2xFLE9BQU8sS0FBSyxFQUFFLGlCQUFpQixFQUFFLE1BQU0seUJBQXlCLENBQUM7QUFDakUsT0FBTyxLQUFLLEVBQUUsbUJBQW1CLEVBQUUsTUFBTSx1Q0FBdUMsQ0FBQztBQUVqRixPQUFPLEVBQUUsS0FBSyxnQkFBZ0IsRUFBOEIsTUFBTSxhQUFhLENBQUM7QUFDaEYsT0FBTyxLQUFLLEVBQUUsbUJBQW1CLEVBQUUscUJBQXFCLEVBQUUsTUFBTSxnQkFBZ0IsQ0FBQztBQUVqRixxQkFBYSxjQUFlLFlBQVcsbUJBQW1CO0lBYXRELE9BQU8sQ0FBQyxRQUFRLENBQUMsSUFBSTtJQVp2QixTQUFTLENBQUMsUUFBUSxDQUFDLEdBQUcsRUFBRSxNQUFNLENBQUM7SUFDL0IsU0FBUyxDQUFDLFFBQVEsQ0FBQyxNQUFNLEVBQUUsZ0JBQWdCLENBQUM7SUFDNUMsU0FBUyxDQUFDLFFBQVEsQ0FBQyxhQUFhLEVBQUUsaUJBQWlCLEdBQUcsU0FBUyxDQUFDO0lBQ2hFLFNBQVMsQ0FBQyxRQUFRLENBQUMsS0FBSyxFQUFFLE9BQU8sS0FBSyxDQUFDO0lBQ3ZDLFNBQVMsQ0FBQyxRQUFRLENBQUMsZ0JBQWdCLEVBQUUsbUJBQW1CLEVBQUUsQ0FBQztJQUMzRCxTQUFTLENBQUMsUUFBUSxDQUFDLHFCQUFxQixFQUFFLG1CQUFtQixHQUFHLFNBQVMsQ0FBQztJQUUxRSxPQUFPLENBQUMsUUFBUSxDQUFTO0lBQ3pCLE9BQU8sQ0FBQywyQkFBMkIsQ0FBQyxDQUFpQjtJQUVyRCxZQUNFLE1BQU0sQ0FBQyxFQUFFLGdCQUFnQixFQUNSLElBQUksR0FBRTtRQUNyQixNQUFNLENBQUMsRUFBRSxNQUFNLENBQUM7UUFDaEIsYUFBYSxDQUFDLEVBQUUsaUJBQWlCLENBQUM7UUFDbEMsZ0JBQWdCLENBQUMsRUFBRSxtQkFBbUIsRUFBRSxDQUFDO1FBQ3pDLHFCQUFxQixDQUFDLEVBQUUsbUJBQW1CLENBQUM7UUFDNUMseUVBQXlFO1FBQ3pFLGNBQWMsQ0FBQyxFQUFFLENBQUMsS0FBSyxFQUFFLElBQUksRUFBRSxLQUFLLElBQUksQ0FBQztLQUNyQyxFQXdCUDtJQUVEOzs7T0FHRztJQUNILE9BQU8sQ0FBQyxzQkFBc0I7SUFVOUI7Ozs7O09BS0c7SUFDSSxXQUFXLENBQUMsS0FBSyxFQUFFLE9BQU8sR0FBRyxJQUFJLENBR3ZDO0lBRVksV0FBVyxrQkFvRXZCO0lBRVksb0JBQW9CLENBQUMsS0FBSyxFQUFFLElBQUksRUFBRSxHQUFHLE9BQU8sQ0FBQyxPQUFPLENBQUMsQ0FtQmpFO0lBRUQ7Ozs7Ozs7Ozs7Ozs7O09BY0c7SUFDVSxjQUFjLENBQ3pCLFNBQVMsRUFBRSxLQUFLLE1BQU0sRUFBRSxFQUN4QixVQUFVLEVBQUUsTUFBTSxFQUFFLEVBQ3BCLElBQUksQ0FBQyxFQUFFLHFCQUFxQixHQUMzQixPQUFPLENBQUMsSUFBSSxFQUFFLENBQUMsQ0E2SWpCO1lBUWEsYUFBYTtJQXNDZCxrQkFBa0IsQ0FDN0IsT0FBTyxFQUFFLE1BQU0sRUFDZixlQUFlLEVBQUUsTUFBTSxHQUFHLE1BQU0sRUFDaEMsVUFBVSxHQUFFLE1BQU0sRUFBTyxFQUN6QixvQkFBb0IsQ0FBQyxFQUFFLE1BQU0sR0FDNUIsT0FBTyxDQUFDLElBQUksRUFBRSxDQUFDLENBR2pCO0lBRVksZ0JBQWdCLENBQzNCLE9BQU8sRUFBRSxNQUFNLEVBQ2YsZUFBZSxFQUFFLE1BQU0sR0FBRyxNQUFNLEVBQ2hDLG9CQUFvQixDQUFDLEVBQUUsTUFBTSxHQUM1QixPQUFPLENBQUMsUUFBUSxFQUFFLENBQUMsQ0FzQ3JCO0lBRUQsT0FBTyxDQUFDLGlCQUFpQjtZQVlYLG1CQUFtQjtZQW1DbkIsYUFBYTtJQTZEM0Isc0NBQXNDO0lBQy9CLGdCQUFnQixJQUFJLGlCQUFpQixHQUFHLFNBQVMsQ0FFdkQ7SUFFRCxpRUFBaUU7SUFDMUQsU0FBUyxJQUFJLE9BQU8sQ0FFMUI7SUFFRDs7O09BR0c7SUFDVSxLQUFLLElBQUksT0FBTyxDQUFDLElBQUksQ0FBQyxDQVNsQztJQUVEOztPQUVHO0lBQ0gsT0FBTyxDQUFDLDhCQUE4QjtJQVd0Qzs7T0FFRztJQUNJLElBQUksSUFBSSxJQUFJLENBS2xCO0NBQ0YifQ==
79
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaHR0cC5kLnRzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2NsaWVudC9odHRwLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxJQUFJLEVBQUUsS0FBSyxRQUFRLEVBQStCLE1BQU0saUJBQWlCLENBQUM7QUFHbkYsT0FBTyxFQUFFLEtBQUssTUFBTSxFQUFnQixNQUFNLHVCQUF1QixDQUFDO0FBT2xFLE9BQU8sS0FBSyxFQUFFLGlCQUFpQixFQUFFLE1BQU0seUJBQXlCLENBQUM7QUFDakUsT0FBTyxLQUFLLEVBQUUsbUJBQW1CLEVBQUUsTUFBTSx1Q0FBdUMsQ0FBQztBQUVqRixPQUFPLEVBQUUsS0FBSyxnQkFBZ0IsRUFBOEIsTUFBTSxhQUFhLENBQUM7QUFDaEYsT0FBTyxLQUFLLEVBQUUsbUJBQW1CLEVBQUUscUJBQXFCLEVBQUUsTUFBTSxnQkFBZ0IsQ0FBQztBQUVqRixxQkFBYSxjQUFlLFlBQVcsbUJBQW1CO0lBYXRELE9BQU8sQ0FBQyxRQUFRLENBQUMsSUFBSTtJQVp2QixTQUFTLENBQUMsUUFBUSxDQUFDLEdBQUcsRUFBRSxNQUFNLENBQUM7SUFDL0IsU0FBUyxDQUFDLFFBQVEsQ0FBQyxNQUFNLEVBQUUsZ0JBQWdCLENBQUM7SUFDNUMsU0FBUyxDQUFDLFFBQVEsQ0FBQyxhQUFhLEVBQUUsaUJBQWlCLEdBQUcsU0FBUyxDQUFDO0lBQ2hFLFNBQVMsQ0FBQyxRQUFRLENBQUMsS0FBSyxFQUFFLE9BQU8sS0FBSyxDQUFDO0lBQ3ZDLFNBQVMsQ0FBQyxRQUFRLENBQUMsZ0JBQWdCLEVBQUUsbUJBQW1CLEVBQUUsQ0FBQztJQUMzRCxTQUFTLENBQUMsUUFBUSxDQUFDLHFCQUFxQixFQUFFLG1CQUFtQixHQUFHLFNBQVMsQ0FBQztJQUUxRSxPQUFPLENBQUMsUUFBUSxDQUFTO0lBQ3pCLE9BQU8sQ0FBQywyQkFBMkIsQ0FBQyxDQUFpQjtJQUVyRCxZQUNFLE1BQU0sQ0FBQyxFQUFFLGdCQUFnQixFQUNSLElBQUksR0FBRTtRQUNyQixNQUFNLENBQUMsRUFBRSxNQUFNLENBQUM7UUFDaEIsYUFBYSxDQUFDLEVBQUUsaUJBQWlCLENBQUM7UUFDbEMsZ0JBQWdCLENBQUMsRUFBRSxtQkFBbUIsRUFBRSxDQUFDO1FBQ3pDLHFCQUFxQixDQUFDLEVBQUUsbUJBQW1CLENBQUM7UUFDNUMseUVBQXlFO1FBQ3pFLGNBQWMsQ0FBQyxFQUFFLENBQUMsS0FBSyxFQUFFLElBQUksRUFBRSxLQUFLLElBQUksQ0FBQztLQUNyQyxFQXdCUDtJQUVEOzs7T0FHRztJQUNILE9BQU8sQ0FBQyxzQkFBc0I7SUFVOUI7Ozs7O09BS0c7SUFDSSxXQUFXLENBQUMsS0FBSyxFQUFFLE9BQU8sR0FBRyxJQUFJLENBR3ZDO0lBRVksV0FBVyxrQkF3R3ZCO0lBRVksb0JBQW9CLENBQUMsS0FBSyxFQUFFLElBQUksRUFBRSxHQUFHLE9BQU8sQ0FBQyxPQUFPLENBQUMsQ0FtQmpFO0lBRUQ7Ozs7Ozs7Ozs7Ozs7O09BY0c7SUFDVSxjQUFjLENBQ3pCLFNBQVMsRUFBRSxLQUFLLE1BQU0sRUFBRSxFQUN4QixVQUFVLEVBQUUsTUFBTSxFQUFFLEVBQ3BCLElBQUksQ0FBQyxFQUFFLHFCQUFxQixHQUMzQixPQUFPLENBQUMsSUFBSSxFQUFFLENBQUMsQ0E2SWpCO1lBUWEsYUFBYTtJQXNDZCxrQkFBa0IsQ0FDN0IsT0FBTyxFQUFFLE1BQU0sRUFDZixlQUFlLEVBQUUsTUFBTSxHQUFHLE1BQU0sRUFDaEMsVUFBVSxHQUFFLE1BQU0sRUFBTyxFQUN6QixvQkFBb0IsQ0FBQyxFQUFFLE1BQU0sR0FDNUIsT0FBTyxDQUFDLElBQUksRUFBRSxDQUFDLENBR2pCO0lBRVksZ0JBQWdCLENBQzNCLE9BQU8sRUFBRSxNQUFNLEVBQ2YsZUFBZSxFQUFFLE1BQU0sR0FBRyxNQUFNLEVBQ2hDLG9CQUFvQixDQUFDLEVBQUUsTUFBTSxHQUM1QixPQUFPLENBQUMsUUFBUSxFQUFFLENBQUMsQ0FzQ3JCO0lBRUQsT0FBTyxDQUFDLGlCQUFpQjtZQVlYLG1CQUFtQjtZQW1DbkIsYUFBYTtJQTZEM0Isc0NBQXNDO0lBQy9CLGdCQUFnQixJQUFJLGlCQUFpQixHQUFHLFNBQVMsQ0FFdkQ7SUFFRCxpRUFBaUU7SUFDMUQsU0FBUyxJQUFJLE9BQU8sQ0FFMUI7SUFFRDs7O09BR0c7SUFDVSxLQUFLLElBQUksT0FBTyxDQUFDLElBQUksQ0FBQyxDQVNsQztJQUVEOztPQUVHO0lBQ0gsT0FBTyxDQUFDLDhCQUE4QjtJQVd0Qzs7T0FFRztJQUNJLElBQUksSUFBSSxJQUFJLENBS2xCO0NBQ0YifQ==
@@ -1 +1 @@
1
- {"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../src/client/http.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,KAAK,QAAQ,EAA+B,MAAM,iBAAiB,CAAC;AAEnF,OAAO,EAAE,KAAK,MAAM,EAAgB,MAAM,uBAAuB,CAAC;AAOlE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AACjE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,uCAAuC,CAAC;AAEjF,OAAO,EAAE,KAAK,gBAAgB,EAA8B,MAAM,aAAa,CAAC;AAChF,OAAO,KAAK,EAAE,mBAAmB,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AAEjF,qBAAa,cAAe,YAAW,mBAAmB;IAatD,OAAO,CAAC,QAAQ,CAAC,IAAI;IAZvB,SAAS,CAAC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IAC/B,SAAS,CAAC,QAAQ,CAAC,MAAM,EAAE,gBAAgB,CAAC;IAC5C,SAAS,CAAC,QAAQ,CAAC,aAAa,EAAE,iBAAiB,GAAG,SAAS,CAAC;IAChE,SAAS,CAAC,QAAQ,CAAC,KAAK,EAAE,OAAO,KAAK,CAAC;IACvC,SAAS,CAAC,QAAQ,CAAC,gBAAgB,EAAE,mBAAmB,EAAE,CAAC;IAC3D,SAAS,CAAC,QAAQ,CAAC,qBAAqB,EAAE,mBAAmB,GAAG,SAAS,CAAC;IAE1E,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,2BAA2B,CAAC,CAAiB;IAErD,YACE,MAAM,CAAC,EAAE,gBAAgB,EACR,IAAI,GAAE;QACrB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,aAAa,CAAC,EAAE,iBAAiB,CAAC;QAClC,gBAAgB,CAAC,EAAE,mBAAmB,EAAE,CAAC;QACzC,qBAAqB,CAAC,EAAE,mBAAmB,CAAC;QAC5C,yEAAyE;QACzE,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,IAAI,CAAC;KACrC,EAwBP;IAED;;;OAGG;IACH,OAAO,CAAC,sBAAsB;IAU9B;;;;;OAKG;IACI,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAGvC;IAEY,WAAW,kBAoEvB;IAEY,oBAAoB,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAmBjE;IAED;;;;;;;;;;;;;;OAcG;IACU,cAAc,CACzB,SAAS,EAAE,KAAK,MAAM,EAAE,EACxB,UAAU,EAAE,MAAM,EAAE,EACpB,IAAI,CAAC,EAAE,qBAAqB,GAC3B,OAAO,CAAC,IAAI,EAAE,CAAC,CA6IjB;YAQa,aAAa;IAsCd,kBAAkB,CAC7B,OAAO,EAAE,MAAM,EACf,eAAe,EAAE,MAAM,GAAG,MAAM,EAChC,UAAU,GAAE,MAAM,EAAO,EACzB,oBAAoB,CAAC,EAAE,MAAM,GAC5B,OAAO,CAAC,IAAI,EAAE,CAAC,CAGjB;IAEY,gBAAgB,CAC3B,OAAO,EAAE,MAAM,EACf,eAAe,EAAE,MAAM,GAAG,MAAM,EAChC,oBAAoB,CAAC,EAAE,MAAM,GAC5B,OAAO,CAAC,QAAQ,EAAE,CAAC,CAsCrB;IAED,OAAO,CAAC,iBAAiB;YAYX,mBAAmB;YAmCnB,aAAa;IA6D3B,sCAAsC;IAC/B,gBAAgB,IAAI,iBAAiB,GAAG,SAAS,CAEvD;IAED,iEAAiE;IAC1D,SAAS,IAAI,OAAO,CAE1B;IAED;;;OAGG;IACU,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CASlC;IAED;;OAEG;IACH,OAAO,CAAC,8BAA8B;IAWtC;;OAEG;IACI,IAAI,IAAI,IAAI,CAKlB;CACF"}
1
+ {"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../src/client/http.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,KAAK,QAAQ,EAA+B,MAAM,iBAAiB,CAAC;AAGnF,OAAO,EAAE,KAAK,MAAM,EAAgB,MAAM,uBAAuB,CAAC;AAOlE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AACjE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,uCAAuC,CAAC;AAEjF,OAAO,EAAE,KAAK,gBAAgB,EAA8B,MAAM,aAAa,CAAC;AAChF,OAAO,KAAK,EAAE,mBAAmB,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AAEjF,qBAAa,cAAe,YAAW,mBAAmB;IAatD,OAAO,CAAC,QAAQ,CAAC,IAAI;IAZvB,SAAS,CAAC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IAC/B,SAAS,CAAC,QAAQ,CAAC,MAAM,EAAE,gBAAgB,CAAC;IAC5C,SAAS,CAAC,QAAQ,CAAC,aAAa,EAAE,iBAAiB,GAAG,SAAS,CAAC;IAChE,SAAS,CAAC,QAAQ,CAAC,KAAK,EAAE,OAAO,KAAK,CAAC;IACvC,SAAS,CAAC,QAAQ,CAAC,gBAAgB,EAAE,mBAAmB,EAAE,CAAC;IAC3D,SAAS,CAAC,QAAQ,CAAC,qBAAqB,EAAE,mBAAmB,GAAG,SAAS,CAAC;IAE1E,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,2BAA2B,CAAC,CAAiB;IAErD,YACE,MAAM,CAAC,EAAE,gBAAgB,EACR,IAAI,GAAE;QACrB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,aAAa,CAAC,EAAE,iBAAiB,CAAC;QAClC,gBAAgB,CAAC,EAAE,mBAAmB,EAAE,CAAC;QACzC,qBAAqB,CAAC,EAAE,mBAAmB,CAAC;QAC5C,yEAAyE;QACzE,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,IAAI,CAAC;KACrC,EAwBP;IAED;;;OAGG;IACH,OAAO,CAAC,sBAAsB;IAU9B;;;;;OAKG;IACI,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAGvC;IAEY,WAAW,kBAwGvB;IAEY,oBAAoB,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAmBjE;IAED;;;;;;;;;;;;;;OAcG;IACU,cAAc,CACzB,SAAS,EAAE,KAAK,MAAM,EAAE,EACxB,UAAU,EAAE,MAAM,EAAE,EACpB,IAAI,CAAC,EAAE,qBAAqB,GAC3B,OAAO,CAAC,IAAI,EAAE,CAAC,CA6IjB;YAQa,aAAa;IAsCd,kBAAkB,CAC7B,OAAO,EAAE,MAAM,EACf,eAAe,EAAE,MAAM,GAAG,MAAM,EAChC,UAAU,GAAE,MAAM,EAAO,EACzB,oBAAoB,CAAC,EAAE,MAAM,GAC5B,OAAO,CAAC,IAAI,EAAE,CAAC,CAGjB;IAEY,gBAAgB,CAC3B,OAAO,EAAE,MAAM,EACf,eAAe,EAAE,MAAM,GAAG,MAAM,EAChC,oBAAoB,CAAC,EAAE,MAAM,GAC5B,OAAO,CAAC,QAAQ,EAAE,CAAC,CAsCrB;IAED,OAAO,CAAC,iBAAiB;YAYX,mBAAmB;YAmCnB,aAAa;IA6D3B,sCAAsC;IAC/B,gBAAgB,IAAI,iBAAiB,GAAG,SAAS,CAEvD;IAED,iEAAiE;IAC1D,SAAS,IAAI,OAAO,CAE1B;IAED;;;OAGG;IACU,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CASlC;IAED;;OAEG;IACH,OAAO,CAAC,8BAA8B;IAWtC;;OAEG;IACI,IAAI,IAAI,IAAI,CAKlB;CACF"}
@@ -1,9 +1,10 @@
1
1
  import { Blob, computeEthVersionedBlobHash } from '@aztec/blob-lib';
2
+ import { makeL1HttpTransport } from '@aztec/ethereum/client';
2
3
  import { shuffle } from '@aztec/foundation/array';
3
4
  import { createLogger } from '@aztec/foundation/log';
4
5
  import { makeBackoff, retry } from '@aztec/foundation/retry';
5
6
  import { bufferToHex, hexToBuffer } from '@aztec/foundation/string';
6
- import { createPublicClient, fallback, http } from 'viem';
7
+ import { createPublicClient } from 'viem';
7
8
  import { createBlobArchiveClient } from '../archive/factory.js';
8
9
  import { DEFAULT_HEALTHCHECK_UPLOAD_INTERVAL_MINUTES } from '../filestore/healthcheck.js';
9
10
  import { getBlobClientConfigFromEnv } from './config.js';
@@ -66,22 +67,50 @@ export class HttpBlobClient {
66
67
  l1ConsensusHostUrls,
67
68
  archiveUrl
68
69
  });
69
- let successfulSourceCount = 0;
70
+ let consensusSuperNodes = 0;
71
+ let consensusNonSuperNodes = 0;
72
+ let archiveSources = 0;
73
+ let blobSinks = 0;
70
74
  if (l1ConsensusHostUrls && l1ConsensusHostUrls.length > 0) {
71
75
  for(let l1ConsensusHostIndex = 0; l1ConsensusHostIndex < l1ConsensusHostUrls.length; l1ConsensusHostIndex++){
72
76
  const l1ConsensusHostUrl = l1ConsensusHostUrls[l1ConsensusHostIndex];
73
77
  try {
74
- const { url, ...options } = getBeaconNodeFetchOptions(`${l1ConsensusHostUrl}/eth/v1/beacon/headers`, this.config, l1ConsensusHostIndex);
78
+ const { url, ...options } = getBeaconNodeFetchOptions(`${l1ConsensusHostUrl}/eth/v1/beacon/headers/head`, this.config, l1ConsensusHostIndex);
75
79
  const res = await this.fetch(url, options);
76
- if (res.ok) {
77
- this.log.info(`L1 consensus host is reachable`, {
80
+ if (!res.ok) {
81
+ this.log.error(`Failure reaching L1 consensus host: ${res.statusText} (${res.status})`, {
78
82
  l1ConsensusHostUrl
79
83
  });
80
- successfulSourceCount++;
84
+ continue;
85
+ }
86
+ this.log.info(`L1 consensus host is reachable`, {
87
+ l1ConsensusHostUrl
88
+ });
89
+ // Check if the host serves blob sidecars (supernode/semi-supernode).
90
+ // Post-Fusaka (PeerDAS), non-supernode beacon nodes no longer serve the
91
+ // blob sidecar endpoint. A 200 response (even with an empty data array
92
+ // for a slot with no blobs) means the node supports serving blob sidecars.
93
+ const body = await res.json();
94
+ const headSlot = body?.data?.header?.message?.slot;
95
+ if (headSlot) {
96
+ const { url: blobUrl, ...blobOptions } = getBeaconNodeFetchOptions(`${l1ConsensusHostUrl}/eth/v1/beacon/blobs/${headSlot}`, this.config, l1ConsensusHostIndex);
97
+ const blobRes = await this.fetch(blobUrl, blobOptions);
98
+ if (blobRes.ok) {
99
+ this.log.info(`L1 consensus host serves blob sidecars (supernode)`, {
100
+ l1ConsensusHostUrl
101
+ });
102
+ consensusSuperNodes++;
103
+ } else {
104
+ this.log.info(`L1 consensus host does not serve blob sidecars`, {
105
+ l1ConsensusHostUrl
106
+ });
107
+ consensusNonSuperNodes++;
108
+ }
81
109
  } else {
82
- this.log.error(`Failure reaching L1 consensus host: ${res.statusText} (${res.status})`, {
110
+ this.log.info(`L1 consensus host is reachable but could not determine head slot`, {
83
111
  l1ConsensusHostUrl
84
112
  });
113
+ consensusNonSuperNodes++;
85
114
  }
86
115
  } catch (err) {
87
116
  this.log.error(`Error reaching L1 consensus host`, err, {
@@ -89,8 +118,6 @@ export class HttpBlobClient {
89
118
  });
90
119
  }
91
120
  }
92
- } else {
93
- this.log.warn('No L1 consensus host urls configured');
94
121
  }
95
122
  if (this.archiveClient) {
96
123
  try {
@@ -99,14 +126,12 @@ export class HttpBlobClient {
99
126
  latest,
100
127
  archiveUrl
101
128
  });
102
- successfulSourceCount++;
129
+ archiveSources++;
103
130
  } catch (err) {
104
131
  this.log.error(`Error reaching archive client`, err, {
105
132
  archiveUrl
106
133
  });
107
134
  }
108
- } else {
109
- this.log.warn('No archive client configured');
110
135
  }
111
136
  if (this.fileStoreClients.length > 0) {
112
137
  for (const fileStoreClient of this.fileStoreClients){
@@ -116,7 +141,7 @@ export class HttpBlobClient {
116
141
  this.log.info(`FileStore is reachable`, {
117
142
  url: fileStoreClient.getBaseUrl()
118
143
  });
119
- successfulSourceCount++;
144
+ blobSinks++;
120
145
  } else {
121
146
  this.log.warn(`FileStore is not accessible`, {
122
147
  url: fileStoreClient.getBaseUrl()
@@ -129,12 +154,22 @@ export class HttpBlobClient {
129
154
  }
130
155
  }
131
156
  }
157
+ // Emit a single summary after validating all sources
158
+ const successfulSourceCount = consensusSuperNodes + archiveSources + blobSinks;
159
+ let summary = `Blob client running with consensusSuperNodes=${consensusSuperNodes} archiveSources=${archiveSources} blobSinks=${blobSinks}`;
160
+ if (consensusNonSuperNodes > 0) {
161
+ summary += `. ${consensusNonSuperNodes} consensus client(s) ignored because they are not running in supernode or semi-supernode mode`;
162
+ }
132
163
  if (successfulSourceCount === 0) {
133
164
  if (this.config.blobAllowEmptySources) {
134
- this.log.warn('No blob sources are reachable');
165
+ this.log.warn(summary);
135
166
  } else {
136
- throw new Error('No blob sources are reachable');
167
+ throw new Error(summary);
137
168
  }
169
+ } else if (consensusSuperNodes === 0) {
170
+ this.log.warn(summary);
171
+ } else {
172
+ this.log.info(summary);
138
173
  }
139
174
  }
140
175
  async sendBlobsToFilestore(blobs) {
@@ -183,8 +218,8 @@ export class HttpBlobClient {
183
218
  // Return the result, ignoring any undefined ones
184
219
  const getFilledBlobs = ()=>resultBlobs.filter((b)=>b !== undefined);
185
220
  // Helper to fill in results from fetched blobs
186
- const fillResults = (fetchedBlobs)=>{
187
- const blobs = processFetchedBlobs(fetchedBlobs, blobHashes, this.log);
221
+ const fillResults = async (fetchedBlobs)=>{
222
+ const blobs = await processFetchedBlobs(fetchedBlobs, blobHashes, this.log);
188
223
  // Fill in any missing positions with matching blobs
189
224
  for(let i = 0; i < blobHashes.length; i++){
190
225
  if (resultBlobs[i] === undefined) {
@@ -236,7 +271,7 @@ export class HttpBlobClient {
236
271
  ...ctx
237
272
  });
238
273
  const blobs = await this.getBlobsFromHost(l1ConsensusHostUrl, slotNumber, l1ConsensusHostIndex);
239
- const result = fillResults(blobs);
274
+ const result = await fillResults(blobs);
240
275
  this.log.debug(`Got ${blobs.length} blobs from consensus host (total: ${result.length}/${blobHashes.length})`, {
241
276
  slotNumber,
242
277
  l1ConsensusHostUrl,
@@ -279,7 +314,7 @@ export class HttpBlobClient {
279
314
  this.log.debug('No blobs found from archive client', archiveCtx);
280
315
  } else {
281
316
  this.log.trace(`Got ${allBlobs.length} blobs from archive client before filtering`, archiveCtx);
282
- const result = fillResults(allBlobs);
317
+ const result = await fillResults(allBlobs);
283
318
  this.log.debug(`Got ${allBlobs.length} blobs from archive client (total: ${result.length}/${blobHashes.length})`, archiveCtx);
284
319
  if (result.length === blobHashes.length) {
285
320
  return returnWithCallback(result);
@@ -320,7 +355,7 @@ export class HttpBlobClient {
320
355
  });
321
356
  const blobs = await client.getBlobsByHashes(blobHashStrings);
322
357
  if (blobs.length > 0) {
323
- const result = fillResults(blobs);
358
+ const result = await fillResults(blobs);
324
359
  this.log.debug(`Got ${blobs.length} blobs from filestore (total: ${result.length}/${ctx.blobHashes.length})`, {
325
360
  url: client.getBaseUrl(),
326
361
  ...ctx
@@ -335,7 +370,7 @@ export class HttpBlobClient {
335
370
  }
336
371
  async getBlobSidecarFrom(hostUrl, blockHashOrSlot, blobHashes = [], l1ConsensusHostIndex) {
337
372
  const blobs = await this.getBlobsFromHost(hostUrl, blockHashOrSlot, l1ConsensusHostIndex);
338
- return processFetchedBlobs(blobs, blobHashes, this.log).filter((b)=>b !== undefined);
373
+ return (await processFetchedBlobs(blobs, blobHashes, this.log)).filter((b)=>b !== undefined);
339
374
  }
340
375
  async getBlobsFromHost(hostUrl, blockHashOrSlot, l1ConsensusHostIndex) {
341
376
  try {
@@ -433,9 +468,9 @@ export class HttpBlobClient {
433
468
  // Ping execution node to get the parentBeaconBlockRoot for this block
434
469
  let parentBeaconBlockRoot;
435
470
  const client = createPublicClient({
436
- transport: fallback(l1RpcUrls.map((url)=>http(url, {
437
- batch: false
438
- })))
471
+ transport: makeL1HttpTransport(l1RpcUrls, {
472
+ timeout: this.config.l1HttpTimeoutMS
473
+ })
439
474
  });
440
475
  try {
441
476
  const res = await client.request({
@@ -530,7 +565,7 @@ function parseBlobJson(data) {
530
565
  }
531
566
  // Returns an array that maps each blob hash to the corresponding blob, or undefined if the blob is not found
532
567
  // or the data does not match the commitment.
533
- function processFetchedBlobs(blobs, blobHashes, logger) {
568
+ async function processFetchedBlobs(blobs, blobHashes, logger) {
534
569
  const requestedBlobHashes = new Set(blobHashes.map(bufferToHex));
535
570
  const hashToBlob = new Map();
536
571
  for (const blobJson of blobs){
@@ -539,7 +574,7 @@ function processFetchedBlobs(blobs, blobHashes, logger) {
539
574
  continue;
540
575
  }
541
576
  try {
542
- const blob = Blob.fromJson(blobJson);
577
+ const blob = await Blob.fromJson(blobJson);
543
578
  hashToBlob.set(hashHex, blob);
544
579
  } catch (err) {
545
580
  // If the above throws, it's likely that the blob commitment does not match the hash of the blob data.
@@ -17,7 +17,7 @@ import { makeRandomBlob } from '@aztec/blob-lib/testing';
17
17
  await cleanup();
18
18
  });
19
19
  it('should send and retrieve blobs by hash', async ()=>{
20
- const blob = makeRandomBlob(5);
20
+ const blob = await makeRandomBlob(5);
21
21
  const blobHash = blob.getEthVersionedBlobHash();
22
22
  await client.sendBlobsToFilestore([
23
23
  blob
@@ -29,9 +29,9 @@ import { makeRandomBlob } from '@aztec/blob-lib/testing';
29
29
  expect(retrievedBlobs[0]).toEqual(blob);
30
30
  });
31
31
  it('should handle multiple blobs', async ()=>{
32
- const blobs = Array.from({
32
+ const blobs = await Promise.all(Array.from({
33
33
  length: 3
34
- }, ()=>makeRandomBlob(7));
34
+ }, ()=>makeRandomBlob(7)));
35
35
  const blobHashes = blobs.map((blob)=>blob.getEthVersionedBlobHash());
36
36
  await client.sendBlobsToFilestore(blobs);
37
37
  const retrievedBlobs = await client.getBlobSidecar(blockId, blobHashes);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aztec/blob-client",
3
- "version": "0.0.1-commit.8afd444",
3
+ "version": "0.0.1-commit.8c0b8ff",
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.8afd444",
60
- "@aztec/ethereum": "0.0.1-commit.8afd444",
61
- "@aztec/foundation": "0.0.1-commit.8afd444",
62
- "@aztec/kv-store": "0.0.1-commit.8afd444",
63
- "@aztec/stdlib": "0.0.1-commit.8afd444",
64
- "@aztec/telemetry-client": "0.0.1-commit.8afd444",
59
+ "@aztec/blob-lib": "0.0.1-commit.8c0b8ff",
60
+ "@aztec/ethereum": "0.0.1-commit.8c0b8ff",
61
+ "@aztec/foundation": "0.0.1-commit.8c0b8ff",
62
+ "@aztec/kv-store": "0.0.1-commit.8c0b8ff",
63
+ "@aztec/stdlib": "0.0.1-commit.8c0b8ff",
64
+ "@aztec/telemetry-client": "0.0.1-commit.8c0b8ff",
65
65
  "express": "^4.21.2",
66
66
  "snappy": "^7.2.2",
67
67
  "source-map-support": "^0.5.21",
@@ -13,7 +13,7 @@ export function describeBlobStore(getBlobStore: () => Promise<BlobStore>) {
13
13
  it('should store and retrieve a blob by hash', async () => {
14
14
  // Create a test blob with random fields
15
15
  const testFields = [Fr.random(), Fr.random(), Fr.random()];
16
- const blob = Blob.fromFields(testFields);
16
+ const blob = await Blob.fromFields(testFields);
17
17
  const blobHash = blob.getEthVersionedBlobHash();
18
18
 
19
19
  // Store the blob
@@ -29,8 +29,8 @@ export function describeBlobStore(getBlobStore: () => Promise<BlobStore>) {
29
29
 
30
30
  it('should handle multiple blobs stored and retrieved by their hashes', async () => {
31
31
  // Create two different blobs
32
- const blob1 = Blob.fromFields([Fr.random(), Fr.random()]);
33
- const blob2 = Blob.fromFields([Fr.random(), Fr.random(), Fr.random()]);
32
+ const blob1 = await Blob.fromFields([Fr.random(), Fr.random()]);
33
+ const blob2 = await Blob.fromFields([Fr.random(), Fr.random(), Fr.random()]);
34
34
 
35
35
  const blobHash1 = blob1.getEthVersionedBlobHash();
36
36
  const blobHash2 = blob2.getEthVersionedBlobHash();
@@ -57,9 +57,9 @@ export function describeBlobStore(getBlobStore: () => Promise<BlobStore>) {
57
57
 
58
58
  it('should handle retrieving subset of stored blobs', async () => {
59
59
  // Store multiple blobs
60
- const blob1 = Blob.fromFields([Fr.random()]);
61
- const blob2 = Blob.fromFields([Fr.random()]);
62
- const blob3 = Blob.fromFields([Fr.random()]);
60
+ const blob1 = await Blob.fromFields([Fr.random()]);
61
+ const blob2 = await Blob.fromFields([Fr.random()]);
62
+ const blob3 = await Blob.fromFields([Fr.random()]);
63
63
 
64
64
  await blobStore.addBlobs([blob1, blob2, blob3]);
65
65
 
@@ -75,7 +75,7 @@ export function describeBlobStore(getBlobStore: () => Promise<BlobStore>) {
75
75
  });
76
76
 
77
77
  it('should handle duplicate blob hashes in request', async () => {
78
- const blob = Blob.fromFields([Fr.random()]);
78
+ const blob = await Blob.fromFields([Fr.random()]);
79
79
  const blobHash = blob.getEthVersionedBlobHash();
80
80
 
81
81
  await blobStore.addBlobs([blob]);
@@ -91,8 +91,8 @@ export function describeBlobStore(getBlobStore: () => Promise<BlobStore>) {
91
91
  it('should overwrite blob when storing with same hash', async () => {
92
92
  // Create two blobs that will have the same hash (same content)
93
93
  const fields = [Fr.random(), Fr.random()];
94
- const blob1 = Blob.fromFields(fields);
95
- const blob2 = Blob.fromFields(fields);
94
+ const blob1 = await Blob.fromFields(fields);
95
+ const blob2 = await Blob.fromFields(fields);
96
96
 
97
97
  const blobHash = blob1.getEthVersionedBlobHash();
98
98
 
@@ -3,6 +3,7 @@ import {
3
3
  SecretValue,
4
4
  booleanConfigHelper,
5
5
  getConfigFromMappings,
6
+ optionalNumberConfigHelper,
6
7
  } from '@aztec/foundation/config';
7
8
 
8
9
  import { type BlobArchiveApiConfig, blobArchiveApiConfigMappings } from '../archive/config.js';
@@ -55,6 +56,9 @@ export interface BlobClientConfig extends BlobArchiveApiConfig {
55
56
  * Interval in minutes for uploading healthcheck file to file store (default: 60 = 1 hour)
56
57
  */
57
58
  blobHealthcheckUploadIntervalMinutes?: number;
59
+
60
+ /** Timeout for HTTP requests to the L1 RPC node in ms. */
61
+ l1HttpTimeoutMS?: number;
58
62
  }
59
63
 
60
64
  export const blobClientConfigMapping: ConfigMappingsType<BlobClientConfig> = {
@@ -108,6 +112,11 @@ export const blobClientConfigMapping: ConfigMappingsType<BlobClientConfig> = {
108
112
  description: 'Interval in minutes for uploading healthcheck file to file store (default: 60 = 1 hour)',
109
113
  parseEnv: (val: string | undefined) => (val ? +val : undefined),
110
114
  },
115
+ l1HttpTimeoutMS: {
116
+ env: 'ETHEREUM_HTTP_TIMEOUT_MS',
117
+ description: 'Timeout for HTTP requests to the L1 RPC node in ms.',
118
+ ...optionalNumberConfigHelper(),
119
+ },
111
120
  ...blobArchiveApiConfigMappings,
112
121
  };
113
122
 
@@ -1,10 +1,11 @@
1
1
  import { Blob, type BlobJson, computeEthVersionedBlobHash } from '@aztec/blob-lib';
2
+ import { makeL1HttpTransport } from '@aztec/ethereum/client';
2
3
  import { shuffle } from '@aztec/foundation/array';
3
4
  import { type Logger, createLogger } from '@aztec/foundation/log';
4
5
  import { makeBackoff, retry } from '@aztec/foundation/retry';
5
6
  import { bufferToHex, hexToBuffer } from '@aztec/foundation/string';
6
7
 
7
- import { type RpcBlock, createPublicClient, fallback, http } from 'viem';
8
+ import { type RpcBlock, createPublicClient } from 'viem';
8
9
 
9
10
  import { createBlobArchiveClient } from '../archive/factory.js';
10
11
  import type { BlobArchiveClient } from '../archive/interface.js';
@@ -89,44 +90,68 @@ export class HttpBlobClient implements BlobClientInterface {
89
90
  const archiveUrl = this.archiveClient?.getBaseUrl();
90
91
  this.log.info(`Testing configured blob sources`, { l1ConsensusHostUrls, archiveUrl });
91
92
 
92
- let successfulSourceCount = 0;
93
+ let consensusSuperNodes = 0;
94
+ let consensusNonSuperNodes = 0;
95
+ let archiveSources = 0;
96
+ let blobSinks = 0;
93
97
 
94
98
  if (l1ConsensusHostUrls && l1ConsensusHostUrls.length > 0) {
95
99
  for (let l1ConsensusHostIndex = 0; l1ConsensusHostIndex < l1ConsensusHostUrls.length; l1ConsensusHostIndex++) {
96
100
  const l1ConsensusHostUrl = l1ConsensusHostUrls[l1ConsensusHostIndex];
97
101
  try {
98
102
  const { url, ...options } = getBeaconNodeFetchOptions(
99
- `${l1ConsensusHostUrl}/eth/v1/beacon/headers`,
103
+ `${l1ConsensusHostUrl}/eth/v1/beacon/headers/head`,
100
104
  this.config,
101
105
  l1ConsensusHostIndex,
102
106
  );
103
107
  const res = await this.fetch(url, options);
104
- if (res.ok) {
105
- this.log.info(`L1 consensus host is reachable`, { l1ConsensusHostUrl });
106
- successfulSourceCount++;
107
- } else {
108
+ if (!res.ok) {
108
109
  this.log.error(`Failure reaching L1 consensus host: ${res.statusText} (${res.status})`, {
109
110
  l1ConsensusHostUrl,
110
111
  });
112
+ continue;
113
+ }
114
+
115
+ this.log.info(`L1 consensus host is reachable`, { l1ConsensusHostUrl });
116
+
117
+ // Check if the host serves blob sidecars (supernode/semi-supernode).
118
+ // Post-Fusaka (PeerDAS), non-supernode beacon nodes no longer serve the
119
+ // blob sidecar endpoint. A 200 response (even with an empty data array
120
+ // for a slot with no blobs) means the node supports serving blob sidecars.
121
+ const body = await res.json();
122
+ const headSlot = body?.data?.header?.message?.slot;
123
+ if (headSlot) {
124
+ const { url: blobUrl, ...blobOptions } = getBeaconNodeFetchOptions(
125
+ `${l1ConsensusHostUrl}/eth/v1/beacon/blobs/${headSlot}`,
126
+ this.config,
127
+ l1ConsensusHostIndex,
128
+ );
129
+ const blobRes = await this.fetch(blobUrl, blobOptions);
130
+ if (blobRes.ok) {
131
+ this.log.info(`L1 consensus host serves blob sidecars (supernode)`, { l1ConsensusHostUrl });
132
+ consensusSuperNodes++;
133
+ } else {
134
+ this.log.info(`L1 consensus host does not serve blob sidecars`, { l1ConsensusHostUrl });
135
+ consensusNonSuperNodes++;
136
+ }
137
+ } else {
138
+ this.log.info(`L1 consensus host is reachable but could not determine head slot`, { l1ConsensusHostUrl });
139
+ consensusNonSuperNodes++;
111
140
  }
112
141
  } catch (err) {
113
142
  this.log.error(`Error reaching L1 consensus host`, err, { l1ConsensusHostUrl });
114
143
  }
115
144
  }
116
- } else {
117
- this.log.warn('No L1 consensus host urls configured');
118
145
  }
119
146
 
120
147
  if (this.archiveClient) {
121
148
  try {
122
149
  const latest = await this.archiveClient.getLatestBlock();
123
150
  this.log.info(`Archive client is reachable and synced to L1 block ${latest.number}`, { latest, archiveUrl });
124
- successfulSourceCount++;
151
+ archiveSources++;
125
152
  } catch (err) {
126
153
  this.log.error(`Error reaching archive client`, err, { archiveUrl });
127
154
  }
128
- } else {
129
- this.log.warn('No archive client configured');
130
155
  }
131
156
 
132
157
  if (this.fileStoreClients.length > 0) {
@@ -135,7 +160,7 @@ export class HttpBlobClient implements BlobClientInterface {
135
160
  const accessible = await fileStoreClient.testConnection();
136
161
  if (accessible) {
137
162
  this.log.info(`FileStore is reachable`, { url: fileStoreClient.getBaseUrl() });
138
- successfulSourceCount++;
163
+ blobSinks++;
139
164
  } else {
140
165
  this.log.warn(`FileStore is not accessible`, { url: fileStoreClient.getBaseUrl() });
141
166
  }
@@ -145,12 +170,24 @@ export class HttpBlobClient implements BlobClientInterface {
145
170
  }
146
171
  }
147
172
 
173
+ // Emit a single summary after validating all sources
174
+ const successfulSourceCount = consensusSuperNodes + archiveSources + blobSinks;
175
+
176
+ let summary = `Blob client running with consensusSuperNodes=${consensusSuperNodes} archiveSources=${archiveSources} blobSinks=${blobSinks}`;
177
+ if (consensusNonSuperNodes > 0) {
178
+ summary += `. ${consensusNonSuperNodes} consensus client(s) ignored because they are not running in supernode or semi-supernode mode`;
179
+ }
180
+
148
181
  if (successfulSourceCount === 0) {
149
182
  if (this.config.blobAllowEmptySources) {
150
- this.log.warn('No blob sources are reachable');
183
+ this.log.warn(summary);
151
184
  } else {
152
- throw new Error('No blob sources are reachable');
185
+ throw new Error(summary);
153
186
  }
187
+ } else if (consensusSuperNodes === 0) {
188
+ this.log.warn(summary);
189
+ } else {
190
+ this.log.info(summary);
154
191
  }
155
192
  }
156
193
 
@@ -215,8 +252,8 @@ export class HttpBlobClient implements BlobClientInterface {
215
252
  const getFilledBlobs = (): Blob[] => resultBlobs.filter((b): b is Blob => b !== undefined);
216
253
 
217
254
  // Helper to fill in results from fetched blobs
218
- const fillResults = (fetchedBlobs: BlobJson[]): Blob[] => {
219
- const blobs = processFetchedBlobs(fetchedBlobs, blobHashes, this.log);
255
+ const fillResults = async (fetchedBlobs: BlobJson[]): Promise<Blob[]> => {
256
+ const blobs = await processFetchedBlobs(fetchedBlobs, blobHashes, this.log);
220
257
  // Fill in any missing positions with matching blobs
221
258
  for (let i = 0; i < blobHashes.length; i++) {
222
259
  if (resultBlobs[i] === undefined) {
@@ -269,7 +306,7 @@ export class HttpBlobClient implements BlobClientInterface {
269
306
  ...ctx,
270
307
  });
271
308
  const blobs = await this.getBlobsFromHost(l1ConsensusHostUrl, slotNumber, l1ConsensusHostIndex);
272
- const result = fillResults(blobs);
309
+ const result = await fillResults(blobs);
273
310
  this.log.debug(
274
311
  `Got ${blobs.length} blobs from consensus host (total: ${result.length}/${blobHashes.length})`,
275
312
  { slotNumber, l1ConsensusHostUrl, ...ctx },
@@ -312,7 +349,7 @@ export class HttpBlobClient implements BlobClientInterface {
312
349
  this.log.debug('No blobs found from archive client', archiveCtx);
313
350
  } else {
314
351
  this.log.trace(`Got ${allBlobs.length} blobs from archive client before filtering`, archiveCtx);
315
- const result = fillResults(allBlobs);
352
+ const result = await fillResults(allBlobs);
316
353
  this.log.debug(
317
354
  `Got ${allBlobs.length} blobs from archive client (total: ${result.length}/${blobHashes.length})`,
318
355
  archiveCtx,
@@ -345,7 +382,7 @@ export class HttpBlobClient implements BlobClientInterface {
345
382
  */
346
383
  private async tryFileStores(
347
384
  getMissingBlobHashes: () => Buffer[],
348
- fillResults: (blobs: BlobJson[]) => Blob[],
385
+ fillResults: (blobs: BlobJson[]) => Promise<Blob[]>,
349
386
  ctx: { blockHash: string; blobHashes: string[] },
350
387
  ): Promise<void> {
351
388
  // Shuffle clients for load distribution
@@ -366,7 +403,7 @@ export class HttpBlobClient implements BlobClientInterface {
366
403
  });
367
404
  const blobs = await client.getBlobsByHashes(blobHashStrings);
368
405
  if (blobs.length > 0) {
369
- const result = fillResults(blobs);
406
+ const result = await fillResults(blobs);
370
407
  this.log.debug(
371
408
  `Got ${blobs.length} blobs from filestore (total: ${result.length}/${ctx.blobHashes.length})`,
372
409
  {
@@ -388,7 +425,7 @@ export class HttpBlobClient implements BlobClientInterface {
388
425
  l1ConsensusHostIndex?: number,
389
426
  ): Promise<Blob[]> {
390
427
  const blobs = await this.getBlobsFromHost(hostUrl, blockHashOrSlot, l1ConsensusHostIndex);
391
- return processFetchedBlobs(blobs, blobHashes, this.log).filter((b): b is Blob => b !== undefined);
428
+ return (await processFetchedBlobs(blobs, blobHashes, this.log)).filter((b): b is Blob => b !== undefined);
392
429
  }
393
430
 
394
431
  public async getBlobsFromHost(
@@ -497,7 +534,7 @@ export class HttpBlobClient implements BlobClientInterface {
497
534
  // Ping execution node to get the parentBeaconBlockRoot for this block
498
535
  let parentBeaconBlockRoot: string | undefined;
499
536
  const client = createPublicClient({
500
- transport: fallback(l1RpcUrls.map(url => http(url, { batch: false }))),
537
+ transport: makeL1HttpTransport(l1RpcUrls, { timeout: this.config.l1HttpTimeoutMS }),
501
538
  });
502
539
  try {
503
540
  const res: RpcBlock = await client.request({
@@ -616,7 +653,11 @@ function parseBlobJson(data: any): BlobJson {
616
653
 
617
654
  // Returns an array that maps each blob hash to the corresponding blob, or undefined if the blob is not found
618
655
  // or the data does not match the commitment.
619
- function processFetchedBlobs(blobs: BlobJson[], blobHashes: Buffer[], logger: Logger): (Blob | undefined)[] {
656
+ async function processFetchedBlobs(
657
+ blobs: BlobJson[],
658
+ blobHashes: Buffer[],
659
+ logger: Logger,
660
+ ): Promise<(Blob | undefined)[]> {
620
661
  const requestedBlobHashes = new Set<string>(blobHashes.map(bufferToHex));
621
662
  const hashToBlob = new Map<string, Blob>();
622
663
  for (const blobJson of blobs) {
@@ -626,7 +667,7 @@ function processFetchedBlobs(blobs: BlobJson[], blobHashes: Buffer[], logger: Lo
626
667
  }
627
668
 
628
669
  try {
629
- const blob = Blob.fromJson(blobJson);
670
+ const blob = await Blob.fromJson(blobJson);
630
671
  hashToBlob.set(hashHex, blob);
631
672
  } catch (err) {
632
673
  // If the above throws, it's likely that the blob commitment does not match the hash of the blob data.
@@ -28,7 +28,7 @@ export function runBlobClientTests(
28
28
  });
29
29
 
30
30
  it('should send and retrieve blobs by hash', async () => {
31
- const blob = makeRandomBlob(5);
31
+ const blob = await makeRandomBlob(5);
32
32
  const blobHash = blob.getEthVersionedBlobHash();
33
33
 
34
34
  await client.sendBlobsToFilestore([blob]);
@@ -39,7 +39,7 @@ export function runBlobClientTests(
39
39
  });
40
40
 
41
41
  it('should handle multiple blobs', async () => {
42
- const blobs = Array.from({ length: 3 }, () => makeRandomBlob(7));
42
+ const blobs = await Promise.all(Array.from({ length: 3 }, () => makeRandomBlob(7)));
43
43
  const blobHashes = blobs.map(blob => blob.getEthVersionedBlobHash());
44
44
 
45
45
  await client.sendBlobsToFilestore(blobs);