@aztec/blob-client 0.0.1-commit.ffe5b04ea → 0.0.1-commit.fff30aa
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.
- package/dest/client/config.d.ts +3 -1
- package/dest/client/config.d.ts.map +1 -1
- package/dest/client/config.js +6 -1
- package/dest/client/http.d.ts +3 -9
- package/dest/client/http.d.ts.map +1 -1
- package/dest/client/http.js +90 -107
- package/dest/client/interface.d.ts +1 -12
- package/dest/client/interface.d.ts.map +1 -1
- package/package.json +7 -7
- package/src/client/config.ts +9 -0
- package/src/client/http.ts +85 -132
- package/src/client/interface.ts +0 -11
package/dest/client/config.d.ts
CHANGED
|
@@ -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,
|
|
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,
|
|
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"}
|
package/dest/client/config.js
CHANGED
|
@@ -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
|
/**
|
package/dest/client/http.d.ts
CHANGED
|
@@ -14,10 +14,6 @@ export declare class HttpBlobClient implements BlobClientInterface {
|
|
|
14
14
|
protected readonly fileStoreUploadClient: FileStoreBlobClient | undefined;
|
|
15
15
|
private disabled;
|
|
16
16
|
private healthcheckUploadIntervalId?;
|
|
17
|
-
/** Cached beacon genesis time (seconds since Unix epoch). Fetched once at startup. */
|
|
18
|
-
private beaconGenesisTime?;
|
|
19
|
-
/** Cached beacon slot duration in seconds. Fetched once at startup. */
|
|
20
|
-
private beaconSecondsPerSlot?;
|
|
21
17
|
constructor(config?: BlobClientConfig, opts?: {
|
|
22
18
|
logger?: Logger;
|
|
23
19
|
archiveClient?: BlobArchiveClient;
|
|
@@ -58,7 +54,7 @@ export declare class HttpBlobClient implements BlobClientInterface {
|
|
|
58
54
|
getBlobSidecar(blockHash: `0x${string}`, blobHashes: Buffer[], opts?: GetBlobSidecarOptions): Promise<Blob[]>;
|
|
59
55
|
private tryFileStores;
|
|
60
56
|
getBlobSidecarFrom(hostUrl: string, blockHashOrSlot: string | number, blobHashes?: Buffer[], l1ConsensusHostIndex?: number): Promise<Blob[]>;
|
|
61
|
-
getBlobsFromHost(hostUrl: string, blockHashOrSlot: string | number, l1ConsensusHostIndex?: number
|
|
57
|
+
getBlobsFromHost(hostUrl: string, blockHashOrSlot: string | number, l1ConsensusHostIndex?: number): Promise<BlobJson[]>;
|
|
62
58
|
private fetchBlobSidecars;
|
|
63
59
|
private getLatestSlotNumber;
|
|
64
60
|
private getSlotNumber;
|
|
@@ -68,18 +64,16 @@ export declare class HttpBlobClient implements BlobClientInterface {
|
|
|
68
64
|
canUpload(): boolean;
|
|
69
65
|
/**
|
|
70
66
|
* Start the blob client.
|
|
71
|
-
*
|
|
72
|
-
* then uploads the initial healthcheck file (awaited) and starts periodic uploads.
|
|
67
|
+
* Uploads the initial healthcheck file (awaited) and starts periodic uploads.
|
|
73
68
|
*/
|
|
74
69
|
start(): Promise<void>;
|
|
75
70
|
/**
|
|
76
71
|
* Start periodic healthcheck upload to the file store to ensure it remains available even if accidentally deleted.
|
|
77
72
|
*/
|
|
78
73
|
private startPeriodicHealthcheckUpload;
|
|
79
|
-
private fetchBeaconConfig;
|
|
80
74
|
/**
|
|
81
75
|
* Stop the blob client, clearing any periodic tasks.
|
|
82
76
|
*/
|
|
83
77
|
stop(): void;
|
|
84
78
|
}
|
|
85
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
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;
|
|
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"}
|
package/dest/client/http.js
CHANGED
|
@@ -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
|
|
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';
|
|
@@ -17,8 +18,6 @@ export class HttpBlobClient {
|
|
|
17
18
|
fileStoreUploadClient;
|
|
18
19
|
disabled;
|
|
19
20
|
healthcheckUploadIntervalId;
|
|
20
|
-
/** Cached beacon genesis time (seconds since Unix epoch). Fetched once at startup. */ beaconGenesisTime;
|
|
21
|
-
/** Cached beacon slot duration in seconds. Fetched once at startup. */ beaconSecondsPerSlot;
|
|
22
21
|
constructor(config, opts = {}){
|
|
23
22
|
this.opts = opts;
|
|
24
23
|
this.disabled = false;
|
|
@@ -68,22 +67,50 @@ export class HttpBlobClient {
|
|
|
68
67
|
l1ConsensusHostUrls,
|
|
69
68
|
archiveUrl
|
|
70
69
|
});
|
|
71
|
-
let
|
|
70
|
+
let consensusSuperNodes = 0;
|
|
71
|
+
let consensusNonSuperNodes = 0;
|
|
72
|
+
let archiveSources = 0;
|
|
73
|
+
let blobSinks = 0;
|
|
72
74
|
if (l1ConsensusHostUrls && l1ConsensusHostUrls.length > 0) {
|
|
73
75
|
for(let l1ConsensusHostIndex = 0; l1ConsensusHostIndex < l1ConsensusHostUrls.length; l1ConsensusHostIndex++){
|
|
74
76
|
const l1ConsensusHostUrl = l1ConsensusHostUrls[l1ConsensusHostIndex];
|
|
75
77
|
try {
|
|
76
|
-
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);
|
|
77
79
|
const res = await this.fetch(url, options);
|
|
78
|
-
if (res.ok) {
|
|
79
|
-
this.log.
|
|
80
|
+
if (!res.ok) {
|
|
81
|
+
this.log.error(`Failure reaching L1 consensus host: ${res.statusText} (${res.status})`, {
|
|
80
82
|
l1ConsensusHostUrl
|
|
81
83
|
});
|
|
82
|
-
|
|
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
|
+
}
|
|
83
109
|
} else {
|
|
84
|
-
this.log.
|
|
110
|
+
this.log.info(`L1 consensus host is reachable but could not determine head slot`, {
|
|
85
111
|
l1ConsensusHostUrl
|
|
86
112
|
});
|
|
113
|
+
consensusNonSuperNodes++;
|
|
87
114
|
}
|
|
88
115
|
} catch (err) {
|
|
89
116
|
this.log.error(`Error reaching L1 consensus host`, err, {
|
|
@@ -91,8 +118,6 @@ export class HttpBlobClient {
|
|
|
91
118
|
});
|
|
92
119
|
}
|
|
93
120
|
}
|
|
94
|
-
} else {
|
|
95
|
-
this.log.warn('No L1 consensus host urls configured');
|
|
96
121
|
}
|
|
97
122
|
if (this.archiveClient) {
|
|
98
123
|
try {
|
|
@@ -101,14 +126,12 @@ export class HttpBlobClient {
|
|
|
101
126
|
latest,
|
|
102
127
|
archiveUrl
|
|
103
128
|
});
|
|
104
|
-
|
|
129
|
+
archiveSources++;
|
|
105
130
|
} catch (err) {
|
|
106
131
|
this.log.error(`Error reaching archive client`, err, {
|
|
107
132
|
archiveUrl
|
|
108
133
|
});
|
|
109
134
|
}
|
|
110
|
-
} else {
|
|
111
|
-
this.log.warn('No archive client configured');
|
|
112
135
|
}
|
|
113
136
|
if (this.fileStoreClients.length > 0) {
|
|
114
137
|
for (const fileStoreClient of this.fileStoreClients){
|
|
@@ -118,7 +141,7 @@ export class HttpBlobClient {
|
|
|
118
141
|
this.log.info(`FileStore is reachable`, {
|
|
119
142
|
url: fileStoreClient.getBaseUrl()
|
|
120
143
|
});
|
|
121
|
-
|
|
144
|
+
blobSinks++;
|
|
122
145
|
} else {
|
|
123
146
|
this.log.warn(`FileStore is not accessible`, {
|
|
124
147
|
url: fileStoreClient.getBaseUrl()
|
|
@@ -131,12 +154,22 @@ export class HttpBlobClient {
|
|
|
131
154
|
}
|
|
132
155
|
}
|
|
133
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
|
+
}
|
|
134
163
|
if (successfulSourceCount === 0) {
|
|
135
164
|
if (this.config.blobAllowEmptySources) {
|
|
136
|
-
this.log.warn(
|
|
165
|
+
this.log.warn(summary);
|
|
137
166
|
} else {
|
|
138
|
-
throw new Error(
|
|
167
|
+
throw new Error(summary);
|
|
139
168
|
}
|
|
169
|
+
} else if (consensusSuperNodes === 0) {
|
|
170
|
+
this.log.warn(summary);
|
|
171
|
+
} else {
|
|
172
|
+
this.log.info(summary);
|
|
140
173
|
}
|
|
141
174
|
}
|
|
142
175
|
async sendBlobsToFilestore(blobs) {
|
|
@@ -222,7 +255,7 @@ export class HttpBlobClient {
|
|
|
222
255
|
...ctx
|
|
223
256
|
};
|
|
224
257
|
this.log.trace(`Attempting to get slot number for block hash`, consensusCtx);
|
|
225
|
-
const slotNumber = await this.getSlotNumber(blockHash
|
|
258
|
+
const slotNumber = await this.getSlotNumber(blockHash);
|
|
226
259
|
this.log.debug(`Got slot number ${slotNumber} from consensus host for querying blobs`, consensusCtx);
|
|
227
260
|
if (slotNumber) {
|
|
228
261
|
let l1ConsensusHostUrl;
|
|
@@ -237,7 +270,7 @@ export class HttpBlobClient {
|
|
|
237
270
|
l1ConsensusHostUrl,
|
|
238
271
|
...ctx
|
|
239
272
|
});
|
|
240
|
-
const blobs = await this.getBlobsFromHost(l1ConsensusHostUrl, slotNumber, l1ConsensusHostIndex
|
|
273
|
+
const blobs = await this.getBlobsFromHost(l1ConsensusHostUrl, slotNumber, l1ConsensusHostIndex);
|
|
241
274
|
const result = await fillResults(blobs);
|
|
242
275
|
this.log.debug(`Got ${blobs.length} blobs from consensus host (total: ${result.length}/${blobHashes.length})`, {
|
|
243
276
|
slotNumber,
|
|
@@ -336,14 +369,14 @@ export class HttpBlobClient {
|
|
|
336
369
|
}
|
|
337
370
|
}
|
|
338
371
|
async getBlobSidecarFrom(hostUrl, blockHashOrSlot, blobHashes = [], l1ConsensusHostIndex) {
|
|
339
|
-
const blobs = await this.getBlobsFromHost(hostUrl, blockHashOrSlot, l1ConsensusHostIndex
|
|
372
|
+
const blobs = await this.getBlobsFromHost(hostUrl, blockHashOrSlot, l1ConsensusHostIndex);
|
|
340
373
|
return (await processFetchedBlobs(blobs, blobHashes, this.log)).filter((b)=>b !== undefined);
|
|
341
374
|
}
|
|
342
|
-
async getBlobsFromHost(hostUrl, blockHashOrSlot, l1ConsensusHostIndex
|
|
375
|
+
async getBlobsFromHost(hostUrl, blockHashOrSlot, l1ConsensusHostIndex) {
|
|
343
376
|
try {
|
|
344
|
-
let res = await this.fetchBlobSidecars(hostUrl, blockHashOrSlot, l1ConsensusHostIndex
|
|
377
|
+
let res = await this.fetchBlobSidecars(hostUrl, blockHashOrSlot, l1ConsensusHostIndex);
|
|
345
378
|
if (res.ok) {
|
|
346
|
-
return
|
|
379
|
+
return parseBlobJsonsFromResponse(await res.json(), this.log);
|
|
347
380
|
}
|
|
348
381
|
if (res.status === 404 && typeof blockHashOrSlot === 'number') {
|
|
349
382
|
const latestSlot = await this.getLatestSlotNumber(hostUrl, l1ConsensusHostIndex);
|
|
@@ -356,9 +389,9 @@ export class HttpBlobClient {
|
|
|
356
389
|
let currentSlot = blockHashOrSlot + 1;
|
|
357
390
|
while(res.status === 404 && maxRetries > 0 && latestSlot !== undefined && currentSlot <= latestSlot){
|
|
358
391
|
this.log.debug(`Trying slot ${currentSlot}`);
|
|
359
|
-
res = await this.fetchBlobSidecars(hostUrl, currentSlot, l1ConsensusHostIndex
|
|
392
|
+
res = await this.fetchBlobSidecars(hostUrl, currentSlot, l1ConsensusHostIndex);
|
|
360
393
|
if (res.ok) {
|
|
361
|
-
return
|
|
394
|
+
return parseBlobJsonsFromResponse(await res.json(), this.log);
|
|
362
395
|
}
|
|
363
396
|
currentSlot++;
|
|
364
397
|
maxRetries--;
|
|
@@ -375,15 +408,8 @@ export class HttpBlobClient {
|
|
|
375
408
|
return [];
|
|
376
409
|
}
|
|
377
410
|
}
|
|
378
|
-
fetchBlobSidecars(hostUrl, blockHashOrSlot, l1ConsensusHostIndex
|
|
379
|
-
|
|
380
|
-
if (blobHashes && blobHashes.length > 0) {
|
|
381
|
-
const params = new URLSearchParams();
|
|
382
|
-
for (const hash of blobHashes){
|
|
383
|
-
params.append('versioned_hashes', `0x${hash.toString('hex')}`);
|
|
384
|
-
}
|
|
385
|
-
baseUrl += `?${params.toString()}`;
|
|
386
|
-
}
|
|
411
|
+
fetchBlobSidecars(hostUrl, blockHashOrSlot, l1ConsensusHostIndex) {
|
|
412
|
+
const baseUrl = `${hostUrl}/eth/v1/beacon/blob_sidecars/${blockHashOrSlot}`;
|
|
387
413
|
const { url, ...options } = getBeaconNodeFetchOptions(baseUrl, this.config, l1ConsensusHostIndex);
|
|
388
414
|
this.log.debug(`Fetching blob sidecar for ${blockHashOrSlot}`, {
|
|
389
415
|
url,
|
|
@@ -429,45 +455,36 @@ export class HttpBlobClient {
|
|
|
429
455
|
*
|
|
430
456
|
* @param blockHash - The block hash
|
|
431
457
|
* @returns The slot number
|
|
432
|
-
*/ async getSlotNumber(blockHash
|
|
458
|
+
*/ async getSlotNumber(blockHash) {
|
|
433
459
|
const { l1ConsensusHostUrls, l1RpcUrls } = this.config;
|
|
434
460
|
if (!l1ConsensusHostUrls || l1ConsensusHostUrls.length === 0) {
|
|
435
461
|
this.log.debug('No consensus host url configured');
|
|
436
462
|
return undefined;
|
|
437
463
|
}
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
this.log.debug(`Computed slot ${slot} from L1 block timestamp`, {
|
|
442
|
-
l1BlockTimestamp
|
|
443
|
-
});
|
|
444
|
-
return slot;
|
|
464
|
+
if (!l1RpcUrls || l1RpcUrls.length === 0) {
|
|
465
|
+
this.log.debug('No execution host url configured');
|
|
466
|
+
return undefined;
|
|
445
467
|
}
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
468
|
+
// Ping execution node to get the parentBeaconBlockRoot for this block
|
|
469
|
+
let parentBeaconBlockRoot;
|
|
470
|
+
const client = createPublicClient({
|
|
471
|
+
transport: makeL1HttpTransport(l1RpcUrls, {
|
|
472
|
+
timeout: this.config.l1HttpTimeoutMS
|
|
473
|
+
})
|
|
474
|
+
});
|
|
475
|
+
try {
|
|
476
|
+
const res = await client.request({
|
|
477
|
+
method: 'eth_getBlockByHash',
|
|
478
|
+
params: [
|
|
479
|
+
blockHash,
|
|
480
|
+
/*tx flag*/ false
|
|
481
|
+
]
|
|
456
482
|
});
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
method: 'eth_getBlockByHash',
|
|
460
|
-
params: [
|
|
461
|
-
blockHash,
|
|
462
|
-
/*tx flag*/ false
|
|
463
|
-
]
|
|
464
|
-
});
|
|
465
|
-
if (res.parentBeaconBlockRoot) {
|
|
466
|
-
parentBeaconBlockRoot = res.parentBeaconBlockRoot;
|
|
467
|
-
}
|
|
468
|
-
} catch (err) {
|
|
469
|
-
this.log.error(`Error getting parent beacon block root`, err);
|
|
483
|
+
if (res.parentBeaconBlockRoot) {
|
|
484
|
+
parentBeaconBlockRoot = res.parentBeaconBlockRoot;
|
|
470
485
|
}
|
|
486
|
+
} catch (err) {
|
|
487
|
+
this.log.error(`Error getting parent beacon block root`, err);
|
|
471
488
|
}
|
|
472
489
|
if (!parentBeaconBlockRoot) {
|
|
473
490
|
this.log.error(`No parent beacon block root found for block ${blockHash}`);
|
|
@@ -499,10 +516,8 @@ export class HttpBlobClient {
|
|
|
499
516
|
}
|
|
500
517
|
/**
|
|
501
518
|
* Start the blob client.
|
|
502
|
-
*
|
|
503
|
-
* then uploads the initial healthcheck file (awaited) and starts periodic uploads.
|
|
519
|
+
* Uploads the initial healthcheck file (awaited) and starts periodic uploads.
|
|
504
520
|
*/ async start() {
|
|
505
|
-
await this.fetchBeaconConfig();
|
|
506
521
|
if (!this.fileStoreUploadClient) {
|
|
507
522
|
return;
|
|
508
523
|
}
|
|
@@ -521,40 +536,6 @@ export class HttpBlobClient {
|
|
|
521
536
|
}, intervalMs);
|
|
522
537
|
}
|
|
523
538
|
/**
|
|
524
|
-
* Fetches and caches beacon genesis time and slot duration from the first available consensus host.
|
|
525
|
-
* These static values enable timestamp-based slot resolution, eliminating the per-fetch headers call.
|
|
526
|
-
* Logs a warning and leaves fields undefined if all hosts fail, callers fall back gracefully.
|
|
527
|
-
*/ async fetchBeaconConfig() {
|
|
528
|
-
const { l1ConsensusHostUrls } = this.config;
|
|
529
|
-
if (!l1ConsensusHostUrls || l1ConsensusHostUrls.length === 0) {
|
|
530
|
-
return;
|
|
531
|
-
}
|
|
532
|
-
for(let i = 0; i < l1ConsensusHostUrls.length; i++){
|
|
533
|
-
try {
|
|
534
|
-
const { url: genesisUrl, ...genesisOptions } = getBeaconNodeFetchOptions(`${l1ConsensusHostUrls[i]}/eth/v1/config/genesis`, this.config, i);
|
|
535
|
-
const { url: specUrl, ...specOptions } = getBeaconNodeFetchOptions(`${l1ConsensusHostUrls[i]}/eth/v1/config/spec`, this.config, i);
|
|
536
|
-
const [genesisRes, specRes] = await Promise.all([
|
|
537
|
-
this.fetch(genesisUrl, genesisOptions),
|
|
538
|
-
this.fetch(specUrl, specOptions)
|
|
539
|
-
]);
|
|
540
|
-
if (genesisRes.ok && specRes.ok) {
|
|
541
|
-
const genesis = await genesisRes.json();
|
|
542
|
-
const spec = await specRes.json();
|
|
543
|
-
this.beaconGenesisTime = BigInt(genesis.data.genesisTime);
|
|
544
|
-
this.beaconSecondsPerSlot = parseInt(spec.data.secondsPerSlot);
|
|
545
|
-
this.log.debug(`Fetched beacon genesis config`, {
|
|
546
|
-
genesisTime: this.beaconGenesisTime,
|
|
547
|
-
secondsPerSlot: this.beaconSecondsPerSlot
|
|
548
|
-
});
|
|
549
|
-
return;
|
|
550
|
-
}
|
|
551
|
-
} catch (err) {
|
|
552
|
-
this.log.warn(`Failed to fetch beacon config from host ${l1ConsensusHostUrls[i]}`, err);
|
|
553
|
-
}
|
|
554
|
-
}
|
|
555
|
-
this.log.warn('Could not fetch beacon genesis config from any consensus host — will use headers call fallback');
|
|
556
|
-
}
|
|
557
|
-
/**
|
|
558
539
|
* Stop the blob client, clearing any periodic tasks.
|
|
559
540
|
*/ stop() {
|
|
560
541
|
if (this.healthcheckUploadIntervalId) {
|
|
@@ -563,9 +544,10 @@ export class HttpBlobClient {
|
|
|
563
544
|
}
|
|
564
545
|
}
|
|
565
546
|
}
|
|
566
|
-
|
|
547
|
+
function parseBlobJsonsFromResponse(response, logger) {
|
|
567
548
|
try {
|
|
568
|
-
|
|
549
|
+
const blobs = response.data.map(parseBlobJson);
|
|
550
|
+
return blobs;
|
|
569
551
|
} catch (err) {
|
|
570
552
|
logger.error(`Error parsing blob json from response`, err);
|
|
571
553
|
return [];
|
|
@@ -575,9 +557,10 @@ async function parseBlobJsonsFromResponse(response, logger) {
|
|
|
575
557
|
// https://ethereum.github.io/beacon-APIs/?urls.primaryName=dev#/Beacon/getBlobSidecars
|
|
576
558
|
// Here we attempt to parse the response data to Buffer, and check the lengths (via Blob's constructor), to avoid
|
|
577
559
|
// throwing an error down the line when calling Blob.fromJson().
|
|
578
|
-
|
|
579
|
-
const blobBuffer = Buffer.from(
|
|
580
|
-
const
|
|
560
|
+
function parseBlobJson(data) {
|
|
561
|
+
const blobBuffer = Buffer.from(data.blob.slice(2), 'hex');
|
|
562
|
+
const commitmentBuffer = Buffer.from(data.kzg_commitment.slice(2), 'hex');
|
|
563
|
+
const blob = new Blob(blobBuffer, commitmentBuffer);
|
|
581
564
|
return blob.toJSON();
|
|
582
565
|
}
|
|
583
566
|
// Returns an array that maps each blob hash to the corresponding blob, or undefined if the blob is not found
|
|
@@ -10,17 +10,6 @@ export interface GetBlobSidecarOptions {
|
|
|
10
10
|
* - Near tip: FileStore first with no retries (data should exist), L1 consensus second (freshest data), then FileStore with retries, then archive (eg. blobscan)
|
|
11
11
|
*/
|
|
12
12
|
isHistoricalSync?: boolean;
|
|
13
|
-
/**
|
|
14
|
-
* The parent beacon block root for the L1 block containing the blobs.
|
|
15
|
-
* If provided, skips the eth_getBlockByHash execution RPC call inside getSlotNumber.
|
|
16
|
-
*/
|
|
17
|
-
parentBeaconBlockRoot?: string;
|
|
18
|
-
/**
|
|
19
|
-
* The timestamp of the L1 execution block containing the blobs.
|
|
20
|
-
* When provided alongside a cached beacon genesis config (fetched at startup), allows computing
|
|
21
|
-
* the beacon slot directly via timestamp math, skipping the beacon headers network call entirely.
|
|
22
|
-
*/
|
|
23
|
-
l1BlockTimestamp?: bigint;
|
|
24
13
|
}
|
|
25
14
|
export interface BlobClientInterface {
|
|
26
15
|
/** Sends the given blobs to the filestore, to be indexed by blob hash. */
|
|
@@ -36,4 +25,4 @@ export interface BlobClientInterface {
|
|
|
36
25
|
/** Returns true if this client can upload blobs to filestore. */
|
|
37
26
|
canUpload(): boolean;
|
|
38
27
|
}
|
|
39
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
28
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW50ZXJmYWNlLmQudHMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvY2xpZW50L2ludGVyZmFjZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssRUFBRSxJQUFJLEVBQUUsTUFBTSxpQkFBaUIsQ0FBQztBQUU1Qzs7R0FFRztBQUNILE1BQU0sV0FBVyxxQkFBcUI7SUFDcEM7Ozs7O09BS0c7SUFDSCxnQkFBZ0IsQ0FBQyxFQUFFLE9BQU8sQ0FBQztDQUM1QjtBQUVELE1BQU0sV0FBVyxtQkFBbUI7SUFDbEMsMEVBQTBFO0lBQzFFLG9CQUFvQixDQUFDLEtBQUssRUFBRSxJQUFJLEVBQUUsR0FBRyxPQUFPLENBQUMsT0FBTyxDQUFDLENBQUM7SUFDdEQscUVBQXFFO0lBQ3JFLGNBQWMsQ0FBQyxPQUFPLEVBQUUsTUFBTSxFQUFFLFVBQVUsQ0FBQyxFQUFFLE1BQU0sRUFBRSxFQUFFLElBQUksQ0FBQyxFQUFFLHFCQUFxQixHQUFHLE9BQU8sQ0FBQyxJQUFJLEVBQUUsQ0FBQyxDQUFDO0lBQ3RHLDZFQUE2RTtJQUM3RSxLQUFLLENBQUMsSUFBSSxPQUFPLENBQUMsSUFBSSxDQUFDLENBQUM7SUFDeEIsb0ZBQW9GO0lBQ3BGLFdBQVcsSUFBSSxPQUFPLENBQUMsSUFBSSxDQUFDLENBQUM7SUFDN0IsMERBQTBEO0lBQzFELElBQUksQ0FBQyxJQUFJLElBQUksQ0FBQztJQUNkLGlFQUFpRTtJQUNqRSxTQUFTLElBQUksT0FBTyxDQUFDO0NBQ3RCIn0=
|
|
@@ -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;
|
|
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;CAC5B;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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aztec/blob-client",
|
|
3
|
-
"version": "0.0.1-commit.
|
|
3
|
+
"version": "0.0.1-commit.fff30aa",
|
|
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.
|
|
60
|
-
"@aztec/ethereum": "0.0.1-commit.
|
|
61
|
-
"@aztec/foundation": "0.0.1-commit.
|
|
62
|
-
"@aztec/kv-store": "0.0.1-commit.
|
|
63
|
-
"@aztec/stdlib": "0.0.1-commit.
|
|
64
|
-
"@aztec/telemetry-client": "0.0.1-commit.
|
|
59
|
+
"@aztec/blob-lib": "0.0.1-commit.fff30aa",
|
|
60
|
+
"@aztec/ethereum": "0.0.1-commit.fff30aa",
|
|
61
|
+
"@aztec/foundation": "0.0.1-commit.fff30aa",
|
|
62
|
+
"@aztec/kv-store": "0.0.1-commit.fff30aa",
|
|
63
|
+
"@aztec/stdlib": "0.0.1-commit.fff30aa",
|
|
64
|
+
"@aztec/telemetry-client": "0.0.1-commit.fff30aa",
|
|
65
65
|
"express": "^4.21.2",
|
|
66
66
|
"snappy": "^7.2.2",
|
|
67
67
|
"source-map-support": "^0.5.21",
|
package/src/client/config.ts
CHANGED
|
@@ -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
|
|
package/src/client/http.ts
CHANGED
|
@@ -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
|
|
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';
|
|
@@ -24,11 +25,6 @@ export class HttpBlobClient implements BlobClientInterface {
|
|
|
24
25
|
private disabled = false;
|
|
25
26
|
private healthcheckUploadIntervalId?: NodeJS.Timeout;
|
|
26
27
|
|
|
27
|
-
/** Cached beacon genesis time (seconds since Unix epoch). Fetched once at startup. */
|
|
28
|
-
private beaconGenesisTime?: bigint;
|
|
29
|
-
/** Cached beacon slot duration in seconds. Fetched once at startup. */
|
|
30
|
-
private beaconSecondsPerSlot?: number;
|
|
31
|
-
|
|
32
28
|
constructor(
|
|
33
29
|
config?: BlobClientConfig,
|
|
34
30
|
private readonly opts: {
|
|
@@ -94,44 +90,68 @@ export class HttpBlobClient implements BlobClientInterface {
|
|
|
94
90
|
const archiveUrl = this.archiveClient?.getBaseUrl();
|
|
95
91
|
this.log.info(`Testing configured blob sources`, { l1ConsensusHostUrls, archiveUrl });
|
|
96
92
|
|
|
97
|
-
let
|
|
93
|
+
let consensusSuperNodes = 0;
|
|
94
|
+
let consensusNonSuperNodes = 0;
|
|
95
|
+
let archiveSources = 0;
|
|
96
|
+
let blobSinks = 0;
|
|
98
97
|
|
|
99
98
|
if (l1ConsensusHostUrls && l1ConsensusHostUrls.length > 0) {
|
|
100
99
|
for (let l1ConsensusHostIndex = 0; l1ConsensusHostIndex < l1ConsensusHostUrls.length; l1ConsensusHostIndex++) {
|
|
101
100
|
const l1ConsensusHostUrl = l1ConsensusHostUrls[l1ConsensusHostIndex];
|
|
102
101
|
try {
|
|
103
102
|
const { url, ...options } = getBeaconNodeFetchOptions(
|
|
104
|
-
`${l1ConsensusHostUrl}/eth/v1/beacon/headers`,
|
|
103
|
+
`${l1ConsensusHostUrl}/eth/v1/beacon/headers/head`,
|
|
105
104
|
this.config,
|
|
106
105
|
l1ConsensusHostIndex,
|
|
107
106
|
);
|
|
108
107
|
const res = await this.fetch(url, options);
|
|
109
|
-
if (res.ok) {
|
|
110
|
-
this.log.info(`L1 consensus host is reachable`, { l1ConsensusHostUrl });
|
|
111
|
-
successfulSourceCount++;
|
|
112
|
-
} else {
|
|
108
|
+
if (!res.ok) {
|
|
113
109
|
this.log.error(`Failure reaching L1 consensus host: ${res.statusText} (${res.status})`, {
|
|
114
110
|
l1ConsensusHostUrl,
|
|
115
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++;
|
|
116
140
|
}
|
|
117
141
|
} catch (err) {
|
|
118
142
|
this.log.error(`Error reaching L1 consensus host`, err, { l1ConsensusHostUrl });
|
|
119
143
|
}
|
|
120
144
|
}
|
|
121
|
-
} else {
|
|
122
|
-
this.log.warn('No L1 consensus host urls configured');
|
|
123
145
|
}
|
|
124
146
|
|
|
125
147
|
if (this.archiveClient) {
|
|
126
148
|
try {
|
|
127
149
|
const latest = await this.archiveClient.getLatestBlock();
|
|
128
150
|
this.log.info(`Archive client is reachable and synced to L1 block ${latest.number}`, { latest, archiveUrl });
|
|
129
|
-
|
|
151
|
+
archiveSources++;
|
|
130
152
|
} catch (err) {
|
|
131
153
|
this.log.error(`Error reaching archive client`, err, { archiveUrl });
|
|
132
154
|
}
|
|
133
|
-
} else {
|
|
134
|
-
this.log.warn('No archive client configured');
|
|
135
155
|
}
|
|
136
156
|
|
|
137
157
|
if (this.fileStoreClients.length > 0) {
|
|
@@ -140,7 +160,7 @@ export class HttpBlobClient implements BlobClientInterface {
|
|
|
140
160
|
const accessible = await fileStoreClient.testConnection();
|
|
141
161
|
if (accessible) {
|
|
142
162
|
this.log.info(`FileStore is reachable`, { url: fileStoreClient.getBaseUrl() });
|
|
143
|
-
|
|
163
|
+
blobSinks++;
|
|
144
164
|
} else {
|
|
145
165
|
this.log.warn(`FileStore is not accessible`, { url: fileStoreClient.getBaseUrl() });
|
|
146
166
|
}
|
|
@@ -150,12 +170,24 @@ export class HttpBlobClient implements BlobClientInterface {
|
|
|
150
170
|
}
|
|
151
171
|
}
|
|
152
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
|
+
|
|
153
181
|
if (successfulSourceCount === 0) {
|
|
154
182
|
if (this.config.blobAllowEmptySources) {
|
|
155
|
-
this.log.warn(
|
|
183
|
+
this.log.warn(summary);
|
|
156
184
|
} else {
|
|
157
|
-
throw new Error(
|
|
185
|
+
throw new Error(summary);
|
|
158
186
|
}
|
|
187
|
+
} else if (consensusSuperNodes === 0) {
|
|
188
|
+
this.log.warn(summary);
|
|
189
|
+
} else {
|
|
190
|
+
this.log.info(summary);
|
|
159
191
|
}
|
|
160
192
|
}
|
|
161
193
|
|
|
@@ -256,7 +288,7 @@ export class HttpBlobClient implements BlobClientInterface {
|
|
|
256
288
|
// The beacon api can query by slot number, so we get that first
|
|
257
289
|
const consensusCtx = { l1ConsensusHostUrls, ...ctx };
|
|
258
290
|
this.log.trace(`Attempting to get slot number for block hash`, consensusCtx);
|
|
259
|
-
const slotNumber = await this.getSlotNumber(blockHash
|
|
291
|
+
const slotNumber = await this.getSlotNumber(blockHash);
|
|
260
292
|
this.log.debug(`Got slot number ${slotNumber} from consensus host for querying blobs`, consensusCtx);
|
|
261
293
|
|
|
262
294
|
if (slotNumber) {
|
|
@@ -273,12 +305,7 @@ export class HttpBlobClient implements BlobClientInterface {
|
|
|
273
305
|
l1ConsensusHostUrl,
|
|
274
306
|
...ctx,
|
|
275
307
|
});
|
|
276
|
-
const blobs = await this.getBlobsFromHost(
|
|
277
|
-
l1ConsensusHostUrl,
|
|
278
|
-
slotNumber,
|
|
279
|
-
l1ConsensusHostIndex,
|
|
280
|
-
getMissingBlobHashes(),
|
|
281
|
-
);
|
|
308
|
+
const blobs = await this.getBlobsFromHost(l1ConsensusHostUrl, slotNumber, l1ConsensusHostIndex);
|
|
282
309
|
const result = await fillResults(blobs);
|
|
283
310
|
this.log.debug(
|
|
284
311
|
`Got ${blobs.length} blobs from consensus host (total: ${result.length}/${blobHashes.length})`,
|
|
@@ -397,7 +424,7 @@ export class HttpBlobClient implements BlobClientInterface {
|
|
|
397
424
|
blobHashes: Buffer[] = [],
|
|
398
425
|
l1ConsensusHostIndex?: number,
|
|
399
426
|
): Promise<Blob[]> {
|
|
400
|
-
const blobs = await this.getBlobsFromHost(hostUrl, blockHashOrSlot, l1ConsensusHostIndex
|
|
427
|
+
const blobs = await this.getBlobsFromHost(hostUrl, blockHashOrSlot, l1ConsensusHostIndex);
|
|
401
428
|
return (await processFetchedBlobs(blobs, blobHashes, this.log)).filter((b): b is Blob => b !== undefined);
|
|
402
429
|
}
|
|
403
430
|
|
|
@@ -405,12 +432,11 @@ export class HttpBlobClient implements BlobClientInterface {
|
|
|
405
432
|
hostUrl: string,
|
|
406
433
|
blockHashOrSlot: string | number,
|
|
407
434
|
l1ConsensusHostIndex?: number,
|
|
408
|
-
blobHashes?: Buffer[],
|
|
409
435
|
): Promise<BlobJson[]> {
|
|
410
436
|
try {
|
|
411
|
-
let res = await this.fetchBlobSidecars(hostUrl, blockHashOrSlot, l1ConsensusHostIndex
|
|
437
|
+
let res = await this.fetchBlobSidecars(hostUrl, blockHashOrSlot, l1ConsensusHostIndex);
|
|
412
438
|
if (res.ok) {
|
|
413
|
-
return
|
|
439
|
+
return parseBlobJsonsFromResponse(await res.json(), this.log);
|
|
414
440
|
}
|
|
415
441
|
|
|
416
442
|
if (res.status === 404 && typeof blockHashOrSlot === 'number') {
|
|
@@ -425,9 +451,9 @@ export class HttpBlobClient implements BlobClientInterface {
|
|
|
425
451
|
let currentSlot = blockHashOrSlot + 1;
|
|
426
452
|
while (res.status === 404 && maxRetries > 0 && latestSlot !== undefined && currentSlot <= latestSlot) {
|
|
427
453
|
this.log.debug(`Trying slot ${currentSlot}`);
|
|
428
|
-
res = await this.fetchBlobSidecars(hostUrl, currentSlot, l1ConsensusHostIndex
|
|
454
|
+
res = await this.fetchBlobSidecars(hostUrl, currentSlot, l1ConsensusHostIndex);
|
|
429
455
|
if (res.ok) {
|
|
430
|
-
return
|
|
456
|
+
return parseBlobJsonsFromResponse(await res.json(), this.log);
|
|
431
457
|
}
|
|
432
458
|
currentSlot++;
|
|
433
459
|
maxRetries--;
|
|
@@ -450,17 +476,8 @@ export class HttpBlobClient implements BlobClientInterface {
|
|
|
450
476
|
hostUrl: string,
|
|
451
477
|
blockHashOrSlot: string | number,
|
|
452
478
|
l1ConsensusHostIndex?: number,
|
|
453
|
-
blobHashes?: Buffer[],
|
|
454
479
|
): Promise<Response> {
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
if (blobHashes && blobHashes.length > 0) {
|
|
458
|
-
const params = new URLSearchParams();
|
|
459
|
-
for (const hash of blobHashes) {
|
|
460
|
-
params.append('versioned_hashes', `0x${hash.toString('hex')}`);
|
|
461
|
-
}
|
|
462
|
-
baseUrl += `?${params.toString()}`;
|
|
463
|
-
}
|
|
480
|
+
const baseUrl = `${hostUrl}/eth/v1/beacon/blob_sidecars/${blockHashOrSlot}`;
|
|
464
481
|
|
|
465
482
|
const { url, ...options } = getBeaconNodeFetchOptions(baseUrl, this.config, l1ConsensusHostIndex);
|
|
466
483
|
this.log.debug(`Fetching blob sidecar for ${blockHashOrSlot}`, { url, ...options });
|
|
@@ -502,50 +519,34 @@ export class HttpBlobClient implements BlobClientInterface {
|
|
|
502
519
|
* @param blockHash - The block hash
|
|
503
520
|
* @returns The slot number
|
|
504
521
|
*/
|
|
505
|
-
private async getSlotNumber(
|
|
506
|
-
blockHash: `0x${string}`,
|
|
507
|
-
parentBeaconBlockRoot?: string,
|
|
508
|
-
l1BlockTimestamp?: bigint,
|
|
509
|
-
): Promise<number | undefined> {
|
|
522
|
+
private async getSlotNumber(blockHash: `0x${string}`): Promise<number | undefined> {
|
|
510
523
|
const { l1ConsensusHostUrls, l1RpcUrls } = this.config;
|
|
511
524
|
if (!l1ConsensusHostUrls || l1ConsensusHostUrls.length === 0) {
|
|
512
525
|
this.log.debug('No consensus host url configured');
|
|
513
526
|
return undefined;
|
|
514
527
|
}
|
|
515
528
|
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
this.beaconGenesisTime !== undefined &&
|
|
520
|
-
this.beaconSecondsPerSlot !== undefined
|
|
521
|
-
) {
|
|
522
|
-
const slot = Number((l1BlockTimestamp - this.beaconGenesisTime) / BigInt(this.beaconSecondsPerSlot));
|
|
523
|
-
this.log.debug(`Computed slot ${slot} from L1 block timestamp`, { l1BlockTimestamp });
|
|
524
|
-
return slot;
|
|
529
|
+
if (!l1RpcUrls || l1RpcUrls.length === 0) {
|
|
530
|
+
this.log.debug('No execution host url configured');
|
|
531
|
+
return undefined;
|
|
525
532
|
}
|
|
526
533
|
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
534
|
+
// Ping execution node to get the parentBeaconBlockRoot for this block
|
|
535
|
+
let parentBeaconBlockRoot: string | undefined;
|
|
536
|
+
const client = createPublicClient({
|
|
537
|
+
transport: makeL1HttpTransport(l1RpcUrls, { timeout: this.config.l1HttpTimeoutMS }),
|
|
538
|
+
});
|
|
539
|
+
try {
|
|
540
|
+
const res: RpcBlock = await client.request({
|
|
541
|
+
method: 'eth_getBlockByHash',
|
|
542
|
+
params: [blockHash, /*tx flag*/ false],
|
|
536
543
|
});
|
|
537
|
-
try {
|
|
538
|
-
const res: RpcBlock = await client.request({
|
|
539
|
-
method: 'eth_getBlockByHash',
|
|
540
|
-
params: [blockHash, /*tx flag*/ false],
|
|
541
|
-
});
|
|
542
544
|
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
}
|
|
546
|
-
} catch (err) {
|
|
547
|
-
this.log.error(`Error getting parent beacon block root`, err);
|
|
545
|
+
if (res.parentBeaconBlockRoot) {
|
|
546
|
+
parentBeaconBlockRoot = res.parentBeaconBlockRoot;
|
|
548
547
|
}
|
|
548
|
+
} catch (err) {
|
|
549
|
+
this.log.error(`Error getting parent beacon block root`, err);
|
|
549
550
|
}
|
|
550
551
|
|
|
551
552
|
if (!parentBeaconBlockRoot) {
|
|
@@ -591,12 +592,9 @@ export class HttpBlobClient implements BlobClientInterface {
|
|
|
591
592
|
|
|
592
593
|
/**
|
|
593
594
|
* Start the blob client.
|
|
594
|
-
*
|
|
595
|
-
* then uploads the initial healthcheck file (awaited) and starts periodic uploads.
|
|
595
|
+
* Uploads the initial healthcheck file (awaited) and starts periodic uploads.
|
|
596
596
|
*/
|
|
597
597
|
public async start(): Promise<void> {
|
|
598
|
-
await this.fetchBeaconConfig();
|
|
599
|
-
|
|
600
598
|
if (!this.fileStoreUploadClient) {
|
|
601
599
|
return;
|
|
602
600
|
}
|
|
@@ -621,53 +619,6 @@ export class HttpBlobClient implements BlobClientInterface {
|
|
|
621
619
|
}, intervalMs);
|
|
622
620
|
}
|
|
623
621
|
|
|
624
|
-
/**
|
|
625
|
-
* Fetches and caches beacon genesis time and slot duration from the first available consensus host.
|
|
626
|
-
* These static values enable timestamp-based slot resolution, eliminating the per-fetch headers call.
|
|
627
|
-
* Logs a warning and leaves fields undefined if all hosts fail, callers fall back gracefully.
|
|
628
|
-
*/
|
|
629
|
-
private async fetchBeaconConfig(): Promise<void> {
|
|
630
|
-
const { l1ConsensusHostUrls } = this.config;
|
|
631
|
-
if (!l1ConsensusHostUrls || l1ConsensusHostUrls.length === 0) {
|
|
632
|
-
return;
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
-
for (let i = 0; i < l1ConsensusHostUrls.length; i++) {
|
|
636
|
-
try {
|
|
637
|
-
const { url: genesisUrl, ...genesisOptions } = getBeaconNodeFetchOptions(
|
|
638
|
-
`${l1ConsensusHostUrls[i]}/eth/v1/config/genesis`,
|
|
639
|
-
this.config,
|
|
640
|
-
i,
|
|
641
|
-
);
|
|
642
|
-
const { url: specUrl, ...specOptions } = getBeaconNodeFetchOptions(
|
|
643
|
-
`${l1ConsensusHostUrls[i]}/eth/v1/config/spec`,
|
|
644
|
-
this.config,
|
|
645
|
-
i,
|
|
646
|
-
);
|
|
647
|
-
|
|
648
|
-
const [genesisRes, specRes] = await Promise.all([
|
|
649
|
-
this.fetch(genesisUrl, genesisOptions),
|
|
650
|
-
this.fetch(specUrl, specOptions),
|
|
651
|
-
]);
|
|
652
|
-
|
|
653
|
-
if (genesisRes.ok && specRes.ok) {
|
|
654
|
-
const genesis = await genesisRes.json();
|
|
655
|
-
const spec = await specRes.json();
|
|
656
|
-
this.beaconGenesisTime = BigInt(genesis.data.genesisTime);
|
|
657
|
-
this.beaconSecondsPerSlot = parseInt(spec.data.secondsPerSlot);
|
|
658
|
-
this.log.debug(`Fetched beacon genesis config`, {
|
|
659
|
-
genesisTime: this.beaconGenesisTime,
|
|
660
|
-
secondsPerSlot: this.beaconSecondsPerSlot,
|
|
661
|
-
});
|
|
662
|
-
return;
|
|
663
|
-
}
|
|
664
|
-
} catch (err) {
|
|
665
|
-
this.log.warn(`Failed to fetch beacon config from host ${l1ConsensusHostUrls[i]}`, err);
|
|
666
|
-
}
|
|
667
|
-
}
|
|
668
|
-
this.log.warn('Could not fetch beacon genesis config from any consensus host — will use headers call fallback');
|
|
669
|
-
}
|
|
670
|
-
|
|
671
622
|
/**
|
|
672
623
|
* Stop the blob client, clearing any periodic tasks.
|
|
673
624
|
*/
|
|
@@ -679,9 +630,10 @@ export class HttpBlobClient implements BlobClientInterface {
|
|
|
679
630
|
}
|
|
680
631
|
}
|
|
681
632
|
|
|
682
|
-
|
|
633
|
+
function parseBlobJsonsFromResponse(response: any, logger: Logger): BlobJson[] {
|
|
683
634
|
try {
|
|
684
|
-
|
|
635
|
+
const blobs = response.data.map(parseBlobJson);
|
|
636
|
+
return blobs;
|
|
685
637
|
} catch (err) {
|
|
686
638
|
logger.error(`Error parsing blob json from response`, err);
|
|
687
639
|
return [];
|
|
@@ -692,9 +644,10 @@ async function parseBlobJsonsFromResponse(response: any, logger: Logger): Promis
|
|
|
692
644
|
// https://ethereum.github.io/beacon-APIs/?urls.primaryName=dev#/Beacon/getBlobSidecars
|
|
693
645
|
// Here we attempt to parse the response data to Buffer, and check the lengths (via Blob's constructor), to avoid
|
|
694
646
|
// throwing an error down the line when calling Blob.fromJson().
|
|
695
|
-
|
|
696
|
-
const blobBuffer = Buffer.from(
|
|
697
|
-
const
|
|
647
|
+
function parseBlobJson(data: any): BlobJson {
|
|
648
|
+
const blobBuffer = Buffer.from(data.blob.slice(2), 'hex');
|
|
649
|
+
const commitmentBuffer = Buffer.from(data.kzg_commitment.slice(2), 'hex');
|
|
650
|
+
const blob = new Blob(blobBuffer, commitmentBuffer);
|
|
698
651
|
return blob.toJSON();
|
|
699
652
|
}
|
|
700
653
|
|
package/src/client/interface.ts
CHANGED
|
@@ -11,17 +11,6 @@ export interface GetBlobSidecarOptions {
|
|
|
11
11
|
* - Near tip: FileStore first with no retries (data should exist), L1 consensus second (freshest data), then FileStore with retries, then archive (eg. blobscan)
|
|
12
12
|
*/
|
|
13
13
|
isHistoricalSync?: boolean;
|
|
14
|
-
/**
|
|
15
|
-
* The parent beacon block root for the L1 block containing the blobs.
|
|
16
|
-
* If provided, skips the eth_getBlockByHash execution RPC call inside getSlotNumber.
|
|
17
|
-
*/
|
|
18
|
-
parentBeaconBlockRoot?: string;
|
|
19
|
-
/**
|
|
20
|
-
* The timestamp of the L1 execution block containing the blobs.
|
|
21
|
-
* When provided alongside a cached beacon genesis config (fetched at startup), allows computing
|
|
22
|
-
* the beacon slot directly via timestamp math, skipping the beacon headers network call entirely.
|
|
23
|
-
*/
|
|
24
|
-
l1BlockTimestamp?: bigint;
|
|
25
14
|
}
|
|
26
15
|
|
|
27
16
|
export interface BlobClientInterface {
|