@aztec/blob-client 3.0.0-nightly.20251223
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/README.md +62 -0
- package/dest/archive/blobscan_archive_client.d.ts +147 -0
- package/dest/archive/blobscan_archive_client.d.ts.map +1 -0
- package/dest/archive/blobscan_archive_client.js +141 -0
- package/dest/archive/config.d.ts +7 -0
- package/dest/archive/config.d.ts.map +1 -0
- package/dest/archive/config.js +11 -0
- package/dest/archive/factory.d.ts +4 -0
- package/dest/archive/factory.d.ts.map +1 -0
- package/dest/archive/factory.js +7 -0
- package/dest/archive/index.d.ts +3 -0
- package/dest/archive/index.d.ts.map +1 -0
- package/dest/archive/index.js +2 -0
- package/dest/archive/instrumentation.d.ts +11 -0
- package/dest/archive/instrumentation.d.ts.map +1 -0
- package/dest/archive/instrumentation.js +33 -0
- package/dest/archive/interface.d.ts +13 -0
- package/dest/archive/interface.d.ts.map +1 -0
- package/dest/archive/interface.js +1 -0
- package/dest/blobstore/blob_store_test_suite.d.ts +3 -0
- package/dest/blobstore/blob_store_test_suite.d.ts.map +1 -0
- package/dest/blobstore/blob_store_test_suite.js +164 -0
- package/dest/blobstore/index.d.ts +3 -0
- package/dest/blobstore/index.d.ts.map +1 -0
- package/dest/blobstore/index.js +2 -0
- package/dest/blobstore/interface.d.ts +12 -0
- package/dest/blobstore/interface.d.ts.map +1 -0
- package/dest/blobstore/interface.js +1 -0
- package/dest/blobstore/memory_blob_store.d.ts +8 -0
- package/dest/blobstore/memory_blob_store.d.ts.map +1 -0
- package/dest/blobstore/memory_blob_store.js +24 -0
- package/dest/client/bin/index.d.ts +3 -0
- package/dest/client/bin/index.d.ts.map +1 -0
- package/dest/client/bin/index.js +30 -0
- package/dest/client/config.d.ts +50 -0
- package/dest/client/config.d.ts.map +1 -0
- package/dest/client/config.js +55 -0
- package/dest/client/factory.d.ts +39 -0
- package/dest/client/factory.d.ts.map +1 -0
- package/dest/client/factory.js +53 -0
- package/dest/client/http.d.ts +63 -0
- package/dest/client/http.d.ts.map +1 -0
- package/dest/client/http.js +536 -0
- package/dest/client/index.d.ts +6 -0
- package/dest/client/index.d.ts.map +1 -0
- package/dest/client/index.js +5 -0
- package/dest/client/interface.d.ts +23 -0
- package/dest/client/interface.d.ts.map +1 -0
- package/dest/client/interface.js +1 -0
- package/dest/client/local.d.ts +12 -0
- package/dest/client/local.d.ts.map +1 -0
- package/dest/client/local.js +18 -0
- package/dest/client/tests.d.ts +11 -0
- package/dest/client/tests.d.ts.map +1 -0
- package/dest/client/tests.js +65 -0
- package/dest/encoding/index.d.ts +15 -0
- package/dest/encoding/index.d.ts.map +1 -0
- package/dest/encoding/index.js +19 -0
- package/dest/filestore/factory.d.ts +50 -0
- package/dest/filestore/factory.d.ts.map +1 -0
- package/dest/filestore/factory.js +67 -0
- package/dest/filestore/filestore_blob_client.d.ts +56 -0
- package/dest/filestore/filestore_blob_client.d.ts.map +1 -0
- package/dest/filestore/filestore_blob_client.js +99 -0
- package/dest/filestore/index.d.ts +3 -0
- package/dest/filestore/index.d.ts.map +1 -0
- package/dest/filestore/index.js +2 -0
- package/dest/types/api.d.ts +65 -0
- package/dest/types/api.d.ts.map +1 -0
- package/dest/types/api.js +22 -0
- package/dest/types/blob_with_index.d.ts +25 -0
- package/dest/types/blob_with_index.d.ts.map +1 -0
- package/dest/types/blob_with_index.js +43 -0
- package/dest/types/index.d.ts +2 -0
- package/dest/types/index.d.ts.map +1 -0
- package/dest/types/index.js +1 -0
- package/package.json +95 -0
- package/src/archive/blobscan_archive_client.ts +178 -0
- package/src/archive/config.ts +14 -0
- package/src/archive/factory.ts +11 -0
- package/src/archive/fixtures/blobscan_get_blob_data.json +1 -0
- package/src/archive/fixtures/blobscan_get_block.json +56 -0
- package/src/archive/index.ts +2 -0
- package/src/archive/instrumentation.ts +41 -0
- package/src/archive/interface.ts +9 -0
- package/src/blobstore/blob_store_test_suite.ts +137 -0
- package/src/blobstore/index.ts +2 -0
- package/src/blobstore/interface.ts +12 -0
- package/src/blobstore/memory_blob_store.ts +31 -0
- package/src/client/bin/index.ts +35 -0
- package/src/client/config.ts +117 -0
- package/src/client/factory.ts +88 -0
- package/src/client/http.ts +620 -0
- package/src/client/index.ts +5 -0
- package/src/client/interface.ts +30 -0
- package/src/client/local.ts +32 -0
- package/src/client/tests.ts +78 -0
- package/src/encoding/index.ts +21 -0
- package/src/filestore/factory.ts +145 -0
- package/src/filestore/filestore_blob_client.ts +129 -0
- package/src/filestore/index.ts +2 -0
- package/src/types/api.ts +50 -0
- package/src/types/blob_with_index.ts +48 -0
- package/src/types/index.ts +1 -0
package/README.md
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
## Blob Client
|
|
2
|
+
|
|
3
|
+
A client library for storing and retrieving blob data from various sources.
|
|
4
|
+
|
|
5
|
+
## When is this used?
|
|
6
|
+
|
|
7
|
+
The blob client is used by nodes to store and retrieve blob data that accompanies `propose` transactions on L1.
|
|
8
|
+
|
|
9
|
+
### Why?
|
|
10
|
+
|
|
11
|
+
Blob data is only available in the L1 consensus layer for a limited period (~3 weeks). The blob client provides a unified interface for:
|
|
12
|
+
- Fetching blobs from L1 consensus layer (beacon nodes)
|
|
13
|
+
- Storing/retrieving blobs from file stores (local files, S3, GCS)
|
|
14
|
+
- Caching blobs locally for faster access
|
|
15
|
+
|
|
16
|
+
### How?
|
|
17
|
+
|
|
18
|
+
The blob client supports multiple blob sources:
|
|
19
|
+
1. **File Store**: Local filesystem (`file://`), S3 (`s3://`), or GCS (`gs://`)
|
|
20
|
+
2. **L1 Consensus**: Beacon node API for recent blobs
|
|
21
|
+
3. **Archive**: Blobscan API for historical blobs
|
|
22
|
+
|
|
23
|
+
### Configurations
|
|
24
|
+
|
|
25
|
+
**File Store URLs** (`BLOB_FILE_STORE_URLS`):
|
|
26
|
+
Comma-separated list of URLs to read blobs from. Tried in order until blobs are found.
|
|
27
|
+
|
|
28
|
+
**File Store Upload URL** (`BLOB_FILE_STORE_UPLOAD_URL`):
|
|
29
|
+
URL for uploading blobs to a file store.
|
|
30
|
+
|
|
31
|
+
**L1 Consensus Host URLs** (`L1_CONSENSUS_HOST_URLS`):
|
|
32
|
+
Beacon node URLs for fetching recent blobs directly from L1.
|
|
33
|
+
|
|
34
|
+
**Archive API URL** (`BLOB_SINK_ARCHIVE_API_URL`):
|
|
35
|
+
Blobscan or similar archive API for historical blob data.
|
|
36
|
+
|
|
37
|
+
### Example Usage
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
import { createBlobClient, createBlobClientWithFileStores } from '@aztec/blob-client/client';
|
|
41
|
+
|
|
42
|
+
// Simple client with L1 consensus and archive sources
|
|
43
|
+
const client = createBlobClient({
|
|
44
|
+
l1ConsensusHostUrls: ['https://beacon.example.com'],
|
|
45
|
+
archiveApiUrl: 'https://api.blobscan.com',
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// Client with file store support
|
|
49
|
+
const clientWithFileStore = await createBlobClientWithFileStores({
|
|
50
|
+
l1ChainId: 1,
|
|
51
|
+
rollupVersion: 1,
|
|
52
|
+
l1Contracts: { rollupAddress: { toString: () => '0x...' } },
|
|
53
|
+
blobFileStoreUrls: ['s3://bucket/blobs', 'file:///local/blobs'],
|
|
54
|
+
blobFileStoreUploadUrl: 's3://bucket/blobs',
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// Fetch blobs
|
|
58
|
+
const blobs = await client.getBlobSidecar(blockHash, blobHashes);
|
|
59
|
+
|
|
60
|
+
// Upload blobs to file store
|
|
61
|
+
await client.sendBlobsToFilestore(blobs);
|
|
62
|
+
```
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import type { BlobJson } from '@aztec/blob-lib/types';
|
|
2
|
+
import { type TelemetryClient } from '@aztec/telemetry-client';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
import type { BlobArchiveClient } from './interface.js';
|
|
5
|
+
export declare const BlobscanBlockResponseSchema: z.ZodEffects<z.ZodObject<{
|
|
6
|
+
hash: z.ZodString;
|
|
7
|
+
slot: z.ZodNumber;
|
|
8
|
+
number: z.ZodNumber;
|
|
9
|
+
transactions: z.ZodArray<z.ZodObject<{
|
|
10
|
+
hash: z.ZodString;
|
|
11
|
+
blobs: z.ZodArray<z.ZodObject<{
|
|
12
|
+
versionedHash: z.ZodString;
|
|
13
|
+
data: z.ZodString;
|
|
14
|
+
commitment: z.ZodString;
|
|
15
|
+
proof: z.ZodString;
|
|
16
|
+
size: z.ZodNumber;
|
|
17
|
+
index: z.ZodOptional<z.ZodNumber>;
|
|
18
|
+
}, "strip", z.ZodTypeAny, {
|
|
19
|
+
versionedHash: string;
|
|
20
|
+
data: string;
|
|
21
|
+
commitment: string;
|
|
22
|
+
proof: string;
|
|
23
|
+
size: number;
|
|
24
|
+
index?: number | undefined;
|
|
25
|
+
}, {
|
|
26
|
+
versionedHash: string;
|
|
27
|
+
data: string;
|
|
28
|
+
commitment: string;
|
|
29
|
+
proof: string;
|
|
30
|
+
size: number;
|
|
31
|
+
index?: number | undefined;
|
|
32
|
+
}>, "many">;
|
|
33
|
+
}, "strip", z.ZodTypeAny, {
|
|
34
|
+
hash: string;
|
|
35
|
+
blobs: {
|
|
36
|
+
versionedHash: string;
|
|
37
|
+
data: string;
|
|
38
|
+
commitment: string;
|
|
39
|
+
proof: string;
|
|
40
|
+
size: number;
|
|
41
|
+
index?: number | undefined;
|
|
42
|
+
}[];
|
|
43
|
+
}, {
|
|
44
|
+
hash: string;
|
|
45
|
+
blobs: {
|
|
46
|
+
versionedHash: string;
|
|
47
|
+
data: string;
|
|
48
|
+
commitment: string;
|
|
49
|
+
proof: string;
|
|
50
|
+
size: number;
|
|
51
|
+
index?: number | undefined;
|
|
52
|
+
}[];
|
|
53
|
+
}>, "many">;
|
|
54
|
+
}, "strip", z.ZodTypeAny, {
|
|
55
|
+
hash: string;
|
|
56
|
+
slot: number;
|
|
57
|
+
number: number;
|
|
58
|
+
transactions: {
|
|
59
|
+
hash: string;
|
|
60
|
+
blobs: {
|
|
61
|
+
versionedHash: string;
|
|
62
|
+
data: string;
|
|
63
|
+
commitment: string;
|
|
64
|
+
proof: string;
|
|
65
|
+
size: number;
|
|
66
|
+
index?: number | undefined;
|
|
67
|
+
}[];
|
|
68
|
+
}[];
|
|
69
|
+
}, {
|
|
70
|
+
hash: string;
|
|
71
|
+
slot: number;
|
|
72
|
+
number: number;
|
|
73
|
+
transactions: {
|
|
74
|
+
hash: string;
|
|
75
|
+
blobs: {
|
|
76
|
+
versionedHash: string;
|
|
77
|
+
data: string;
|
|
78
|
+
commitment: string;
|
|
79
|
+
proof: string;
|
|
80
|
+
size: number;
|
|
81
|
+
index?: number | undefined;
|
|
82
|
+
}[];
|
|
83
|
+
}[];
|
|
84
|
+
}>, {
|
|
85
|
+
blob: string;
|
|
86
|
+
kzg_commitment: string;
|
|
87
|
+
index: string;
|
|
88
|
+
}[], {
|
|
89
|
+
hash: string;
|
|
90
|
+
slot: number;
|
|
91
|
+
number: number;
|
|
92
|
+
transactions: {
|
|
93
|
+
hash: string;
|
|
94
|
+
blobs: {
|
|
95
|
+
versionedHash: string;
|
|
96
|
+
data: string;
|
|
97
|
+
commitment: string;
|
|
98
|
+
proof: string;
|
|
99
|
+
size: number;
|
|
100
|
+
index?: number | undefined;
|
|
101
|
+
}[];
|
|
102
|
+
}[];
|
|
103
|
+
}>;
|
|
104
|
+
export declare const BlobscanBlocksResponseSchema: z.ZodObject<{
|
|
105
|
+
blocks: z.ZodArray<z.ZodObject<{
|
|
106
|
+
hash: z.ZodString;
|
|
107
|
+
slot: z.ZodNumber;
|
|
108
|
+
number: z.ZodNumber;
|
|
109
|
+
}, "strip", z.ZodTypeAny, {
|
|
110
|
+
hash: string;
|
|
111
|
+
slot: number;
|
|
112
|
+
number: number;
|
|
113
|
+
}, {
|
|
114
|
+
hash: string;
|
|
115
|
+
slot: number;
|
|
116
|
+
number: number;
|
|
117
|
+
}>, "many">;
|
|
118
|
+
}, "strip", z.ZodTypeAny, {
|
|
119
|
+
blocks: {
|
|
120
|
+
hash: string;
|
|
121
|
+
slot: number;
|
|
122
|
+
number: number;
|
|
123
|
+
}[];
|
|
124
|
+
}, {
|
|
125
|
+
blocks: {
|
|
126
|
+
hash: string;
|
|
127
|
+
slot: number;
|
|
128
|
+
number: number;
|
|
129
|
+
}[];
|
|
130
|
+
}>;
|
|
131
|
+
export declare class BlobscanArchiveClient implements BlobArchiveClient {
|
|
132
|
+
private readonly logger;
|
|
133
|
+
private readonly fetchOpts;
|
|
134
|
+
private readonly fetch;
|
|
135
|
+
private readonly baseUrl;
|
|
136
|
+
private instrumentation;
|
|
137
|
+
constructor(baseUrl: string, telemetry?: TelemetryClient);
|
|
138
|
+
getLatestBlock(): Promise<{
|
|
139
|
+
hash: string;
|
|
140
|
+
number: number;
|
|
141
|
+
slot: number;
|
|
142
|
+
}>;
|
|
143
|
+
getBaseUrl(): string;
|
|
144
|
+
getBlobsFromBlock(blockId: string): Promise<BlobJson[] | undefined>;
|
|
145
|
+
getBlobData(id: string): Promise<Buffer | undefined>;
|
|
146
|
+
}
|
|
147
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYmxvYnNjYW5fYXJjaGl2ZV9jbGllbnQuZC50cyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9hcmNoaXZlL2Jsb2JzY2FuX2FyY2hpdmVfY2xpZW50LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxFQUFFLFFBQVEsRUFBRSxNQUFNLHVCQUF1QixDQUFDO0FBSXRELE9BQU8sRUFBRSxLQUFLLGVBQWUsRUFBc0IsTUFBTSx5QkFBeUIsQ0FBQztBQUVuRixPQUFPLEVBQUUsQ0FBQyxFQUFFLE1BQU0sS0FBSyxDQUFDO0FBR3hCLE9BQU8sS0FBSyxFQUFFLGlCQUFpQixFQUFFLE1BQU0sZ0JBQWdCLENBQUM7QUFFeEQsZUFBTyxNQUFNLDJCQUEyQjs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7RUFpQ3ZDLENBQUM7QUFHRixlQUFPLE1BQU0sNEJBQTRCOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztFQVF2QyxDQUFDO0FBRUgscUJBQWEscUJBQXNCLFlBQVcsaUJBQWlCO0lBQzdELE9BQU8sQ0FBQyxRQUFRLENBQUMsTUFBTSxDQUF1RDtJQUM5RSxPQUFPLENBQUMsUUFBUSxDQUFDLFNBQVMsQ0FBK0M7SUFDekUsT0FBTyxDQUFDLFFBQVEsQ0FBQyxLQUFLLENBU3BCO0lBRUYsT0FBTyxDQUFDLFFBQVEsQ0FBQyxPQUFPLENBQU07SUFFOUIsT0FBTyxDQUFDLGVBQWUsQ0FBbUM7SUFFMUQsWUFBWSxPQUFPLEVBQUUsTUFBTSxFQUFFLFNBQVMsR0FBRSxlQUFzQyxFQVU3RTtJQUVZLGNBQWMsSUFBSSxPQUFPLENBQUM7UUFBRSxJQUFJLEVBQUUsTUFBTSxDQUFDO1FBQUMsTUFBTSxFQUFFLE1BQU0sQ0FBQztRQUFDLElBQUksRUFBRSxNQUFNLENBQUE7S0FBRSxDQUFDLENBMkJyRjtJQUVNLFVBQVUsSUFBSSxNQUFNLENBRTFCO0lBRVksaUJBQWlCLENBQUMsT0FBTyxFQUFFLE1BQU0sR0FBRyxPQUFPLENBQUMsUUFBUSxFQUFFLEdBQUcsU0FBUyxDQUFDLENBNEIvRTtJQUVZLFdBQVcsQ0FBQyxFQUFFLEVBQUUsTUFBTSxHQUFHLE9BQU8sQ0FBQyxNQUFNLEdBQUcsU0FBUyxDQUFDLENBMEJoRTtDQUNGIn0=
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"blobscan_archive_client.d.ts","sourceRoot":"","sources":["../../src/archive/blobscan_archive_client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAItD,OAAO,EAAE,KAAK,eAAe,EAAsB,MAAM,yBAAyB,CAAC;AAEnF,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAExD,eAAO,MAAM,2BAA2B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAiCvC,CAAC;AAGF,eAAO,MAAM,4BAA4B;;;;;;;;;;;;;;;;;;;;;;;;;;EAQvC,CAAC;AAEH,qBAAa,qBAAsB,YAAW,iBAAiB;IAC7D,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAuD;IAC9E,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA+C;IACzE,OAAO,CAAC,QAAQ,CAAC,KAAK,CASpB;IAEF,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAM;IAE9B,OAAO,CAAC,eAAe,CAAmC;IAE1D,YAAY,OAAO,EAAE,MAAM,EAAE,SAAS,GAAE,eAAsC,EAU7E;IAEY,cAAc,IAAI,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CA2BrF;IAEM,UAAU,IAAI,MAAM,CAE1B;IAEY,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,EAAE,GAAG,SAAS,CAAC,CA4B/E;IAEY,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CA0BhE;CACF"}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { createLogger } from '@aztec/foundation/log';
|
|
2
|
+
import { makeBackoff, retry } from '@aztec/foundation/retry';
|
|
3
|
+
import { schemas, zodFor } from '@aztec/foundation/schemas';
|
|
4
|
+
import { getTelemetryClient } from '@aztec/telemetry-client';
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
import { BlobArchiveClientInstrumentation } from './instrumentation.js';
|
|
7
|
+
export const BlobscanBlockResponseSchema = zodFor()(z.object({
|
|
8
|
+
hash: z.string(),
|
|
9
|
+
slot: z.number().int(),
|
|
10
|
+
number: z.number().int(),
|
|
11
|
+
transactions: z.array(z.object({
|
|
12
|
+
hash: z.string(),
|
|
13
|
+
blobs: z.array(z.object({
|
|
14
|
+
versionedHash: z.string(),
|
|
15
|
+
data: z.string(),
|
|
16
|
+
commitment: z.string(),
|
|
17
|
+
proof: z.string(),
|
|
18
|
+
size: z.number().int(),
|
|
19
|
+
index: z.number().int().optional()
|
|
20
|
+
}))
|
|
21
|
+
}))
|
|
22
|
+
}).transform((data)=>data.transactions.flatMap((tx)=>tx.blobs.map((blob)=>({
|
|
23
|
+
blob: blob.data,
|
|
24
|
+
// eslint-disable-next-line camelcase
|
|
25
|
+
kzg_commitment: blob.commitment
|
|
26
|
+
}))).map((blob, index)=>({
|
|
27
|
+
...blob,
|
|
28
|
+
index: index.toString()
|
|
29
|
+
}))));
|
|
30
|
+
// Response from https://api.blobscan.com/blocks?sort=desc&type=canonical
|
|
31
|
+
export const BlobscanBlocksResponseSchema = z.object({
|
|
32
|
+
blocks: z.array(z.object({
|
|
33
|
+
hash: z.string(),
|
|
34
|
+
slot: z.number().int(),
|
|
35
|
+
number: z.number().int()
|
|
36
|
+
}))
|
|
37
|
+
});
|
|
38
|
+
export class BlobscanArchiveClient {
|
|
39
|
+
logger = createLogger('blob-client:blobscan-archive-client');
|
|
40
|
+
fetchOpts = {
|
|
41
|
+
headers: {
|
|
42
|
+
accept: 'application/json'
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
fetch = async (...args)=>{
|
|
46
|
+
return await retry(()=>fetch(...args), // eslint-disable-next-line @typescript-eslint/no-base-to-string
|
|
47
|
+
`Fetching ${args[0]}`, makeBackoff([
|
|
48
|
+
1,
|
|
49
|
+
1,
|
|
50
|
+
3
|
|
51
|
+
]), this.logger, /*failSilently=*/ false);
|
|
52
|
+
};
|
|
53
|
+
baseUrl;
|
|
54
|
+
instrumentation;
|
|
55
|
+
constructor(baseUrl, telemetry = getTelemetryClient()){
|
|
56
|
+
this.baseUrl = new URL(baseUrl);
|
|
57
|
+
if (this.baseUrl.protocol !== 'https:') {
|
|
58
|
+
throw new TypeError('BaseURL must be secure: ' + baseUrl);
|
|
59
|
+
}
|
|
60
|
+
this.instrumentation = new BlobArchiveClientInstrumentation(telemetry, this.baseUrl.origin, 'BlobscanArchiveClient');
|
|
61
|
+
}
|
|
62
|
+
async getLatestBlock() {
|
|
63
|
+
const url = new URL('blocks', this.baseUrl);
|
|
64
|
+
url.searchParams.set('sort', 'desc');
|
|
65
|
+
url.searchParams.set('type', 'canonical');
|
|
66
|
+
url.searchParams.set('p', '1');
|
|
67
|
+
url.searchParams.set('ps', '1');
|
|
68
|
+
this.logger.trace(`Fetching latest block from ${url.href}`);
|
|
69
|
+
const response = await this.fetch(url, this.fetchOpts);
|
|
70
|
+
if (response.status !== 200) {
|
|
71
|
+
throw new Error(`Failed to fetch latest block: ${response.statusText} (${response.status})`, {
|
|
72
|
+
cause: {
|
|
73
|
+
httpResponse: {
|
|
74
|
+
status: response.status,
|
|
75
|
+
body: await response.text().catch(()=>'Failed to read response body')
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
const parsed = await response.json().then((data)=>BlobscanBlocksResponseSchema.parse(data));
|
|
81
|
+
if (parsed.blocks.length === 0) {
|
|
82
|
+
throw new Error(`No blocks found at ${this.baseUrl}`);
|
|
83
|
+
}
|
|
84
|
+
return parsed.blocks[0];
|
|
85
|
+
}
|
|
86
|
+
getBaseUrl() {
|
|
87
|
+
return this.baseUrl.href;
|
|
88
|
+
}
|
|
89
|
+
async getBlobsFromBlock(blockId) {
|
|
90
|
+
const url = new URL(`blocks/${blockId}`, this.baseUrl);
|
|
91
|
+
url.searchParams.set('type', 'canonical');
|
|
92
|
+
url.searchParams.set('expand', 'blob,blob_data');
|
|
93
|
+
this.logger.trace(`Fetching blobs for block ${blockId} from ${url.href}`);
|
|
94
|
+
const response = await this.fetch(url, this.fetchOpts);
|
|
95
|
+
this.instrumentation.incRequest('blocks', response.status);
|
|
96
|
+
if (response.status === 404) {
|
|
97
|
+
this.logger.debug(`No blobs found for block ${blockId} at ${this.baseUrl}`);
|
|
98
|
+
return undefined;
|
|
99
|
+
} else if (response.status !== 200) {
|
|
100
|
+
throw new Error(`Failed to fetch blobs for block ${blockId}: ${response.statusText} (${response.status})`, {
|
|
101
|
+
cause: {
|
|
102
|
+
httpResponse: {
|
|
103
|
+
status: response.status,
|
|
104
|
+
body: await response.text().catch(()=>'Failed to read response body')
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
} else {
|
|
109
|
+
const result = await response.json().then((data)=>BlobscanBlockResponseSchema.parse(data));
|
|
110
|
+
this.logger.debug(`Fetched ${result.length} blobs for block ${blockId} from ${this.baseUrl}`);
|
|
111
|
+
this.instrumentation.incRetrievedBlobs(result.length);
|
|
112
|
+
return result;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
async getBlobData(id) {
|
|
116
|
+
const url = new URL(`blobs/${id}/data`, this.baseUrl);
|
|
117
|
+
const response = await this.fetch(url, this.fetchOpts);
|
|
118
|
+
this.instrumentation.incRequest('blobs', response.status);
|
|
119
|
+
if (response.status === 404) {
|
|
120
|
+
return undefined;
|
|
121
|
+
} else if (response.status !== 200) {
|
|
122
|
+
throw new Error(`Failed to fetch blob data for blob ${id}: ${response.statusText} (${response.status})`, {
|
|
123
|
+
cause: {
|
|
124
|
+
httpResponse: {
|
|
125
|
+
status: response.status,
|
|
126
|
+
body: await response.text().catch((err)=>{
|
|
127
|
+
this.logger.warn('Failed to read response body', err);
|
|
128
|
+
return '';
|
|
129
|
+
})
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
} else {
|
|
134
|
+
return await response.json().then((data)=>{
|
|
135
|
+
const blob = schemas.BufferHex.parse(data);
|
|
136
|
+
this.instrumentation.incRetrievedBlobs(1);
|
|
137
|
+
return blob;
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type L1ReaderConfig } from '@aztec/ethereum/l1-reader';
|
|
2
|
+
import { type ConfigMappingsType } from '@aztec/foundation/config';
|
|
3
|
+
export type BlobArchiveApiConfig = {
|
|
4
|
+
archiveApiUrl?: string;
|
|
5
|
+
} & Partial<Pick<L1ReaderConfig, 'l1ChainId'>>;
|
|
6
|
+
export declare const blobArchiveApiConfigMappings: ConfigMappingsType<BlobArchiveApiConfig>;
|
|
7
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29uZmlnLmQudHMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvYXJjaGl2ZS9jb25maWcudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLEtBQUssY0FBYyxFQUEwQixNQUFNLDJCQUEyQixDQUFDO0FBQ3hGLE9BQU8sRUFBRSxLQUFLLGtCQUFrQixFQUFzQixNQUFNLDBCQUEwQixDQUFDO0FBRXZGLE1BQU0sTUFBTSxvQkFBb0IsR0FBRztJQUNqQyxhQUFhLENBQUMsRUFBRSxNQUFNLENBQUM7Q0FDeEIsR0FBRyxPQUFPLENBQUMsSUFBSSxDQUFDLGNBQWMsRUFBRSxXQUFXLENBQUMsQ0FBQyxDQUFDO0FBRS9DLGVBQU8sTUFBTSw0QkFBNEIsRUFBRSxrQkFBa0IsQ0FBQyxvQkFBb0IsQ0FNakYsQ0FBQyJ9
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/archive/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,cAAc,EAA0B,MAAM,2BAA2B,CAAC;AACxF,OAAO,EAAE,KAAK,kBAAkB,EAAsB,MAAM,0BAA0B,CAAC;AAEvF,MAAM,MAAM,oBAAoB,GAAG;IACjC,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,GAAG,OAAO,CAAC,IAAI,CAAC,cAAc,EAAE,WAAW,CAAC,CAAC,CAAC;AAE/C,eAAO,MAAM,4BAA4B,EAAE,kBAAkB,CAAC,oBAAoB,CAMjF,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { l1ReaderConfigMappings } from '@aztec/ethereum/l1-reader';
|
|
2
|
+
import { pickConfigMappings } from '@aztec/foundation/config';
|
|
3
|
+
export const blobArchiveApiConfigMappings = {
|
|
4
|
+
archiveApiUrl: {
|
|
5
|
+
env: 'BLOB_SINK_ARCHIVE_API_URL',
|
|
6
|
+
description: 'The URL of the archive API'
|
|
7
|
+
},
|
|
8
|
+
...pickConfigMappings(l1ReaderConfigMappings, [
|
|
9
|
+
'l1ChainId'
|
|
10
|
+
])
|
|
11
|
+
};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { BlobClientConfig } from '../client/config.js';
|
|
2
|
+
import type { BlobArchiveClient } from './interface.js';
|
|
3
|
+
export declare function createBlobArchiveClient(config: BlobClientConfig): BlobArchiveClient | undefined;
|
|
4
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZmFjdG9yeS5kLnRzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2FyY2hpdmUvZmFjdG9yeS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssRUFBRSxnQkFBZ0IsRUFBRSxNQUFNLHFCQUFxQixDQUFDO0FBRTVELE9BQU8sS0FBSyxFQUFFLGlCQUFpQixFQUFFLE1BQU0sZ0JBQWdCLENBQUM7QUFFeEQsd0JBQWdCLHVCQUF1QixDQUFDLE1BQU0sRUFBRSxnQkFBZ0IsR0FBRyxpQkFBaUIsR0FBRyxTQUFTLENBTS9GIn0=
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../../src/archive/factory.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAE5D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAExD,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,gBAAgB,GAAG,iBAAiB,GAAG,SAAS,CAM/F"}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export * from './interface.js';
|
|
2
|
+
export * from './factory.js';
|
|
3
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguZC50cyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9hcmNoaXZlL2luZGV4LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLGNBQWMsZ0JBQWdCLENBQUM7QUFDL0IsY0FBYyxjQUFjLENBQUMifQ==
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/archive/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,cAAc,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type TelemetryClient } from '@aztec/telemetry-client';
|
|
2
|
+
export declare class BlobArchiveClientInstrumentation {
|
|
3
|
+
private httpHost;
|
|
4
|
+
private blockRequestCounter;
|
|
5
|
+
private blobRequestCounter;
|
|
6
|
+
private retrievedBlobs;
|
|
7
|
+
constructor(client: TelemetryClient, httpHost: string, name: string);
|
|
8
|
+
incRequest(type: 'blocks' | 'blobs', status: number): void;
|
|
9
|
+
incRetrievedBlobs(count: number): void;
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5zdHJ1bWVudGF0aW9uLmQudHMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvYXJjaGl2ZS9pbnN0cnVtZW50YXRpb24udHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUF1QixLQUFLLGVBQWUsRUFBaUMsTUFBTSx5QkFBeUIsQ0FBQztBQUVuSCxxQkFBYSxnQ0FBZ0M7SUFPekMsT0FBTyxDQUFDLFFBQVE7SUFObEIsT0FBTyxDQUFDLG1CQUFtQixDQUFnQjtJQUMzQyxPQUFPLENBQUMsa0JBQWtCLENBQWdCO0lBQzFDLE9BQU8sQ0FBQyxjQUFjLENBQWdCO0lBRXRDLFlBQ0UsTUFBTSxFQUFFLGVBQWUsRUFDZixRQUFRLEVBQUUsTUFBTSxFQUN4QixJQUFJLEVBQUUsTUFBTSxFQWlCYjtJQUVELFVBQVUsQ0FBQyxJQUFJLEVBQUUsUUFBUSxHQUFHLE9BQU8sRUFBRSxNQUFNLEVBQUUsTUFBTSxRQU1sRDtJQUVELGlCQUFpQixDQUFDLEtBQUssRUFBRSxNQUFNLFFBRTlCO0NBQ0YifQ==
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"instrumentation.d.ts","sourceRoot":"","sources":["../../src/archive/instrumentation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAuB,KAAK,eAAe,EAAiC,MAAM,yBAAyB,CAAC;AAEnH,qBAAa,gCAAgC;IAOzC,OAAO,CAAC,QAAQ;IANlB,OAAO,CAAC,mBAAmB,CAAgB;IAC3C,OAAO,CAAC,kBAAkB,CAAgB;IAC1C,OAAO,CAAC,cAAc,CAAgB;IAEtC,YACE,MAAM,EAAE,eAAe,EACf,QAAQ,EAAE,MAAM,EACxB,IAAI,EAAE,MAAM,EAiBb;IAED,UAAU,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,EAAE,MAAM,EAAE,MAAM,QAMlD;IAED,iBAAiB,CAAC,KAAK,EAAE,MAAM,QAE9B;CACF"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Attributes, Metrics, ValueType } from '@aztec/telemetry-client';
|
|
2
|
+
export class BlobArchiveClientInstrumentation {
|
|
3
|
+
httpHost;
|
|
4
|
+
blockRequestCounter;
|
|
5
|
+
blobRequestCounter;
|
|
6
|
+
retrievedBlobs;
|
|
7
|
+
constructor(client, httpHost, name){
|
|
8
|
+
this.httpHost = httpHost;
|
|
9
|
+
const meter = client.getMeter(name);
|
|
10
|
+
this.blockRequestCounter = meter.createUpDownCounter(Metrics.BLOB_SINK_ARCHIVE_BLOCK_REQUEST_COUNT, {
|
|
11
|
+
description: 'Number of requests made to retrieve blocks from the blob archive',
|
|
12
|
+
valueType: ValueType.INT
|
|
13
|
+
});
|
|
14
|
+
this.blobRequestCounter = meter.createUpDownCounter(Metrics.BLOB_SINK_ARCHIVE_BLOB_REQUEST_COUNT, {
|
|
15
|
+
description: 'Number of requests made to retrieve blobs from the blob archive',
|
|
16
|
+
valueType: ValueType.INT
|
|
17
|
+
});
|
|
18
|
+
this.retrievedBlobs = meter.createUpDownCounter(Metrics.BLOB_SINK_ARCHIVE_BLOB_COUNT, {
|
|
19
|
+
description: 'Number of blobs retrieved from the blob archive',
|
|
20
|
+
valueType: ValueType.INT
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
incRequest(type, status) {
|
|
24
|
+
const counter = type === 'blocks' ? this.blockRequestCounter : this.blobRequestCounter;
|
|
25
|
+
counter.add(1, {
|
|
26
|
+
[Attributes.HTTP_RESPONSE_STATUS_CODE]: status,
|
|
27
|
+
[Attributes.HTTP_REQUEST_HOST]: this.httpHost
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
incRetrievedBlobs(count) {
|
|
31
|
+
this.retrievedBlobs.add(count);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { BlobJson } from '@aztec/blob-lib/types';
|
|
2
|
+
/** Interface to an blob archiving service. */
|
|
3
|
+
export interface BlobArchiveClient {
|
|
4
|
+
getBlobData(id: string): Promise<Buffer | undefined>;
|
|
5
|
+
getBlobsFromBlock(blockId: string): Promise<BlobJson[] | undefined>;
|
|
6
|
+
getLatestBlock(): Promise<{
|
|
7
|
+
hash: string;
|
|
8
|
+
number: number;
|
|
9
|
+
slot: number;
|
|
10
|
+
}>;
|
|
11
|
+
getBaseUrl(): string;
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW50ZXJmYWNlLmQudHMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvYXJjaGl2ZS9pbnRlcmZhY2UudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLEVBQUUsUUFBUSxFQUFFLE1BQU0sdUJBQXVCLENBQUM7QUFFdEQsOENBQThDO0FBQzlDLE1BQU0sV0FBVyxpQkFBaUI7SUFDaEMsV0FBVyxDQUFDLEVBQUUsRUFBRSxNQUFNLEdBQUcsT0FBTyxDQUFDLE1BQU0sR0FBRyxTQUFTLENBQUMsQ0FBQztJQUNyRCxpQkFBaUIsQ0FBQyxPQUFPLEVBQUUsTUFBTSxHQUFHLE9BQU8sQ0FBQyxRQUFRLEVBQUUsR0FBRyxTQUFTLENBQUMsQ0FBQztJQUNwRSxjQUFjLElBQUksT0FBTyxDQUFDO1FBQUUsSUFBSSxFQUFFLE1BQU0sQ0FBQztRQUFDLE1BQU0sRUFBRSxNQUFNLENBQUM7UUFBQyxJQUFJLEVBQUUsTUFBTSxDQUFBO0tBQUUsQ0FBQyxDQUFDO0lBQzFFLFVBQVUsSUFBSSxNQUFNLENBQUM7Q0FDdEIifQ==
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"interface.d.ts","sourceRoot":"","sources":["../../src/archive/interface.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAEtD,8CAA8C;AAC9C,MAAM,WAAW,iBAAiB;IAChC,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IACrD,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,EAAE,GAAG,SAAS,CAAC,CAAC;IACpE,cAAc,IAAI,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC1E,UAAU,IAAI,MAAM,CAAC;CACtB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
/** Interface to an blob archiving service. */ export { };
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { BlobStore } from './interface.js';
|
|
2
|
+
export declare function describeBlobStore(getBlobStore: () => Promise<BlobStore>): void;
|
|
3
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYmxvYl9zdG9yZV90ZXN0X3N1aXRlLmQudHMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvYmxvYnN0b3JlL2Jsb2Jfc3RvcmVfdGVzdF9zdWl0ZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFJQSxPQUFPLEtBQUssRUFBRSxTQUFTLEVBQUUsTUFBTSxnQkFBZ0IsQ0FBQztBQUVoRCx3QkFBZ0IsaUJBQWlCLENBQUMsWUFBWSxFQUFFLE1BQU0sT0FBTyxDQUFDLFNBQVMsQ0FBQyxRQWtJdkUifQ==
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"blob_store_test_suite.d.ts","sourceRoot":"","sources":["../../src/blobstore/blob_store_test_suite.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAEhD,wBAAgB,iBAAiB,CAAC,YAAY,EAAE,MAAM,OAAO,CAAC,SAAS,CAAC,QAkIvE"}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { Blob } from '@aztec/blob-lib';
|
|
2
|
+
import { Fr } from '@aztec/foundation/curves/bn254';
|
|
3
|
+
import { BlobWithIndex } from '../types/index.js';
|
|
4
|
+
export function describeBlobStore(getBlobStore) {
|
|
5
|
+
let blobStore;
|
|
6
|
+
beforeEach(async ()=>{
|
|
7
|
+
blobStore = await getBlobStore();
|
|
8
|
+
});
|
|
9
|
+
it('should store and retrieve a blob by hash', async ()=>{
|
|
10
|
+
// Create a test blob with random fields
|
|
11
|
+
const testFields = [
|
|
12
|
+
Fr.random(),
|
|
13
|
+
Fr.random(),
|
|
14
|
+
Fr.random()
|
|
15
|
+
];
|
|
16
|
+
const blob = Blob.fromFields(testFields);
|
|
17
|
+
const blobWithIndex = new BlobWithIndex(blob, 0);
|
|
18
|
+
const blobHash = blob.getEthVersionedBlobHash();
|
|
19
|
+
// Store the blob
|
|
20
|
+
await blobStore.addBlobs([
|
|
21
|
+
blobWithIndex
|
|
22
|
+
]);
|
|
23
|
+
// Retrieve the blob by hash
|
|
24
|
+
const retrievedBlobs = await blobStore.getBlobsByHashes([
|
|
25
|
+
blobHash
|
|
26
|
+
]);
|
|
27
|
+
// Verify the blob was retrieved and matches
|
|
28
|
+
expect(retrievedBlobs.length).toBe(1);
|
|
29
|
+
expect(retrievedBlobs[0].blob).toEqual(blob);
|
|
30
|
+
});
|
|
31
|
+
it('should handle multiple blobs stored and retrieved by their hashes', async ()=>{
|
|
32
|
+
// Create two different blobs
|
|
33
|
+
const blob1 = Blob.fromFields([
|
|
34
|
+
Fr.random(),
|
|
35
|
+
Fr.random()
|
|
36
|
+
]);
|
|
37
|
+
const blob2 = Blob.fromFields([
|
|
38
|
+
Fr.random(),
|
|
39
|
+
Fr.random(),
|
|
40
|
+
Fr.random()
|
|
41
|
+
]);
|
|
42
|
+
const blobWithIndex1 = new BlobWithIndex(blob1, 0);
|
|
43
|
+
const blobWithIndex2 = new BlobWithIndex(blob2, 1);
|
|
44
|
+
const blobHash1 = blob1.getEthVersionedBlobHash();
|
|
45
|
+
const blobHash2 = blob2.getEthVersionedBlobHash();
|
|
46
|
+
// Store both blobs
|
|
47
|
+
await blobStore.addBlobs([
|
|
48
|
+
blobWithIndex1,
|
|
49
|
+
blobWithIndex2
|
|
50
|
+
]);
|
|
51
|
+
// Retrieve and verify both blobs
|
|
52
|
+
const retrievedBlobs = await blobStore.getBlobsByHashes([
|
|
53
|
+
blobHash1,
|
|
54
|
+
blobHash2
|
|
55
|
+
]);
|
|
56
|
+
expect(retrievedBlobs.length).toBe(2);
|
|
57
|
+
expect(retrievedBlobs[0].blob).toEqual(blob1);
|
|
58
|
+
expect(retrievedBlobs[1].blob).toEqual(blob2);
|
|
59
|
+
});
|
|
60
|
+
it('should return empty array for non-existent blob hash', async ()=>{
|
|
61
|
+
// Create a random hash that doesn't exist
|
|
62
|
+
const nonExistentHash = Buffer.alloc(32);
|
|
63
|
+
nonExistentHash.fill(0xff);
|
|
64
|
+
const retrievedBlobs = await blobStore.getBlobsByHashes([
|
|
65
|
+
nonExistentHash
|
|
66
|
+
]);
|
|
67
|
+
expect(retrievedBlobs).toEqual([]);
|
|
68
|
+
});
|
|
69
|
+
it('should handle storing blobs with different indices', async ()=>{
|
|
70
|
+
// Create blobs with different indices
|
|
71
|
+
const blob1 = Blob.fromFields([
|
|
72
|
+
Fr.random()
|
|
73
|
+
]);
|
|
74
|
+
const blob2 = Blob.fromFields([
|
|
75
|
+
Fr.random()
|
|
76
|
+
]);
|
|
77
|
+
const blobWithIndex1 = new BlobWithIndex(blob1, 0);
|
|
78
|
+
const blobWithIndex2 = new BlobWithIndex(blob2, 1);
|
|
79
|
+
await blobStore.addBlobs([
|
|
80
|
+
blobWithIndex1,
|
|
81
|
+
blobWithIndex2
|
|
82
|
+
]);
|
|
83
|
+
const blobHash1 = blob1.getEthVersionedBlobHash();
|
|
84
|
+
const blobHash2 = blob2.getEthVersionedBlobHash();
|
|
85
|
+
const retrievedBlobs = await blobStore.getBlobsByHashes([
|
|
86
|
+
blobHash1,
|
|
87
|
+
blobHash2
|
|
88
|
+
]);
|
|
89
|
+
expect(retrievedBlobs[0].index).toBe(0);
|
|
90
|
+
expect(retrievedBlobs[1].index).toBe(1);
|
|
91
|
+
});
|
|
92
|
+
it('should handle retrieving subset of stored blobs', async ()=>{
|
|
93
|
+
// Store multiple blobs
|
|
94
|
+
const blob1 = Blob.fromFields([
|
|
95
|
+
Fr.random()
|
|
96
|
+
]);
|
|
97
|
+
const blob2 = Blob.fromFields([
|
|
98
|
+
Fr.random()
|
|
99
|
+
]);
|
|
100
|
+
const blob3 = Blob.fromFields([
|
|
101
|
+
Fr.random()
|
|
102
|
+
]);
|
|
103
|
+
await blobStore.addBlobs([
|
|
104
|
+
new BlobWithIndex(blob1, 0),
|
|
105
|
+
new BlobWithIndex(blob2, 1),
|
|
106
|
+
new BlobWithIndex(blob3, 2)
|
|
107
|
+
]);
|
|
108
|
+
// Retrieve only some of them
|
|
109
|
+
const blobHash1 = blob1.getEthVersionedBlobHash();
|
|
110
|
+
const blobHash3 = blob3.getEthVersionedBlobHash();
|
|
111
|
+
const retrievedBlobs = await blobStore.getBlobsByHashes([
|
|
112
|
+
blobHash1,
|
|
113
|
+
blobHash3
|
|
114
|
+
]);
|
|
115
|
+
expect(retrievedBlobs.length).toBe(2);
|
|
116
|
+
expect(retrievedBlobs[0].blob).toEqual(blob1);
|
|
117
|
+
expect(retrievedBlobs[1].blob).toEqual(blob3);
|
|
118
|
+
});
|
|
119
|
+
it('should handle duplicate blob hashes in request', async ()=>{
|
|
120
|
+
const blob = Blob.fromFields([
|
|
121
|
+
Fr.random()
|
|
122
|
+
]);
|
|
123
|
+
const blobWithIndex = new BlobWithIndex(blob, 0);
|
|
124
|
+
const blobHash = blob.getEthVersionedBlobHash();
|
|
125
|
+
await blobStore.addBlobs([
|
|
126
|
+
blobWithIndex
|
|
127
|
+
]);
|
|
128
|
+
// Request the same blob hash multiple times
|
|
129
|
+
const retrievedBlobs = await blobStore.getBlobsByHashes([
|
|
130
|
+
blobHash,
|
|
131
|
+
blobHash
|
|
132
|
+
]);
|
|
133
|
+
// Implementation may return duplicates or deduplicate - both are valid
|
|
134
|
+
expect(retrievedBlobs.length).toBeGreaterThanOrEqual(1);
|
|
135
|
+
expect(retrievedBlobs[0].blob).toEqual(blob);
|
|
136
|
+
});
|
|
137
|
+
it('should overwrite blob when storing with same hash', async ()=>{
|
|
138
|
+
// Create two blobs that will have the same hash (same content)
|
|
139
|
+
const fields = [
|
|
140
|
+
Fr.random(),
|
|
141
|
+
Fr.random()
|
|
142
|
+
];
|
|
143
|
+
const blob1 = Blob.fromFields(fields);
|
|
144
|
+
const blob2 = Blob.fromFields(fields);
|
|
145
|
+
// Store with different indices
|
|
146
|
+
const blobWithIndex1 = new BlobWithIndex(blob1, 0);
|
|
147
|
+
const blobWithIndex2 = new BlobWithIndex(blob2, 5);
|
|
148
|
+
const blobHash = blob1.getEthVersionedBlobHash();
|
|
149
|
+
// Store first blob
|
|
150
|
+
await blobStore.addBlobs([
|
|
151
|
+
blobWithIndex1
|
|
152
|
+
]);
|
|
153
|
+
// Overwrite with second blob (same hash, different index)
|
|
154
|
+
await blobStore.addBlobs([
|
|
155
|
+
blobWithIndex2
|
|
156
|
+
]);
|
|
157
|
+
// Retrieve and verify it's the second blob (with index 5)
|
|
158
|
+
const retrievedBlobs = await blobStore.getBlobsByHashes([
|
|
159
|
+
blobHash
|
|
160
|
+
]);
|
|
161
|
+
expect(retrievedBlobs.length).toBe(1);
|
|
162
|
+
expect(retrievedBlobs[0].index).toBe(5);
|
|
163
|
+
});
|
|
164
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export * from './memory_blob_store.js';
|
|
2
|
+
export * from './interface.js';
|
|
3
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguZC50cyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9ibG9ic3RvcmUvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsY0FBYyx3QkFBd0IsQ0FBQztBQUN2QyxjQUFjLGdCQUFnQixDQUFDIn0=
|