@aztec/blob-client 0.0.1-commit.03f7ef2
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 +146 -0
- package/dest/archive/blobscan_archive_client.d.ts.map +1 -0
- package/dest/archive/blobscan_archive_client.js +138 -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 +133 -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 +533 -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 +22 -0
- package/dest/client/interface.d.ts.map +1 -0
- package/dest/client/interface.js +1 -0
- package/dest/client/local.d.ts +11 -0
- package/dest/client/local.d.ts.map +1 -0
- package/dest/client/local.js +16 -0
- package/dest/client/tests.d.ts +11 -0
- package/dest/client/tests.d.ts.map +1 -0
- package/dest/client/tests.js +51 -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 +55 -0
- package/dest/filestore/filestore_blob_client.d.ts.map +1 -0
- package/dest/filestore/filestore_blob_client.js +91 -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/package.json +94 -0
- package/src/archive/blobscan_archive_client.ts +176 -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 +110 -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 +616 -0
- package/src/client/index.ts +5 -0
- package/src/client/interface.ts +23 -0
- package/src/client/local.ts +25 -0
- package/src/client/tests.ts +62 -0
- package/src/encoding/index.ts +21 -0
- package/src/filestore/factory.ts +145 -0
- package/src/filestore/filestore_blob_client.ts +121 -0
- package/src/filestore/index.ts +2 -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,146 @@
|
|
|
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
|
+
}[], {
|
|
88
|
+
hash: string;
|
|
89
|
+
slot: number;
|
|
90
|
+
number: number;
|
|
91
|
+
transactions: {
|
|
92
|
+
hash: string;
|
|
93
|
+
blobs: {
|
|
94
|
+
versionedHash: string;
|
|
95
|
+
data: string;
|
|
96
|
+
commitment: string;
|
|
97
|
+
proof: string;
|
|
98
|
+
size: number;
|
|
99
|
+
index?: number | undefined;
|
|
100
|
+
}[];
|
|
101
|
+
}[];
|
|
102
|
+
}>;
|
|
103
|
+
export declare const BlobscanBlocksResponseSchema: z.ZodObject<{
|
|
104
|
+
blocks: z.ZodArray<z.ZodObject<{
|
|
105
|
+
hash: z.ZodString;
|
|
106
|
+
slot: z.ZodNumber;
|
|
107
|
+
number: z.ZodNumber;
|
|
108
|
+
}, "strip", z.ZodTypeAny, {
|
|
109
|
+
hash: string;
|
|
110
|
+
slot: number;
|
|
111
|
+
number: number;
|
|
112
|
+
}, {
|
|
113
|
+
hash: string;
|
|
114
|
+
slot: number;
|
|
115
|
+
number: number;
|
|
116
|
+
}>, "many">;
|
|
117
|
+
}, "strip", z.ZodTypeAny, {
|
|
118
|
+
blocks: {
|
|
119
|
+
hash: string;
|
|
120
|
+
slot: number;
|
|
121
|
+
number: number;
|
|
122
|
+
}[];
|
|
123
|
+
}, {
|
|
124
|
+
blocks: {
|
|
125
|
+
hash: string;
|
|
126
|
+
slot: number;
|
|
127
|
+
number: number;
|
|
128
|
+
}[];
|
|
129
|
+
}>;
|
|
130
|
+
export declare class BlobscanArchiveClient implements BlobArchiveClient {
|
|
131
|
+
private readonly logger;
|
|
132
|
+
private readonly fetchOpts;
|
|
133
|
+
private readonly fetch;
|
|
134
|
+
private readonly baseUrl;
|
|
135
|
+
private instrumentation;
|
|
136
|
+
constructor(baseUrl: string, telemetry?: TelemetryClient);
|
|
137
|
+
getLatestBlock(): Promise<{
|
|
138
|
+
hash: string;
|
|
139
|
+
number: number;
|
|
140
|
+
slot: number;
|
|
141
|
+
}>;
|
|
142
|
+
getBaseUrl(): string;
|
|
143
|
+
getBlobsFromBlock(blockId: string): Promise<BlobJson[] | undefined>;
|
|
144
|
+
getBlobData(id: string): Promise<Buffer | undefined>;
|
|
145
|
+
}
|
|
146
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYmxvYnNjYW5fYXJjaGl2ZV9jbGllbnQuZC50cyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9hcmNoaXZlL2Jsb2JzY2FuX2FyY2hpdmVfY2xpZW50LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxFQUFFLFFBQVEsRUFBRSxNQUFNLHVCQUF1QixDQUFDO0FBSXRELE9BQU8sRUFBRSxLQUFLLGVBQWUsRUFBc0IsTUFBTSx5QkFBeUIsQ0FBQztBQUVuRixPQUFPLEVBQUUsQ0FBQyxFQUFFLE1BQU0sS0FBSyxDQUFDO0FBR3hCLE9BQU8sS0FBSyxFQUFFLGlCQUFpQixFQUFFLE1BQU0sZ0JBQWdCLENBQUM7QUFFeEQsZUFBTyxNQUFNLDJCQUEyQjs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztFQStCdkMsQ0FBQztBQUdGLGVBQU8sTUFBTSw0QkFBNEI7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0VBUXZDLENBQUM7QUFFSCxxQkFBYSxxQkFBc0IsWUFBVyxpQkFBaUI7SUFDN0QsT0FBTyxDQUFDLFFBQVEsQ0FBQyxNQUFNLENBQXVEO0lBQzlFLE9BQU8sQ0FBQyxRQUFRLENBQUMsU0FBUyxDQUErQztJQUN6RSxPQUFPLENBQUMsUUFBUSxDQUFDLEtBQUssQ0FTcEI7SUFFRixPQUFPLENBQUMsUUFBUSxDQUFDLE9BQU8sQ0FBTTtJQUU5QixPQUFPLENBQUMsZUFBZSxDQUFtQztJQUUxRCxZQUFZLE9BQU8sRUFBRSxNQUFNLEVBQUUsU0FBUyxHQUFFLGVBQXNDLEVBVTdFO0lBRVksY0FBYyxJQUFJLE9BQU8sQ0FBQztRQUFFLElBQUksRUFBRSxNQUFNLENBQUM7UUFBQyxNQUFNLEVBQUUsTUFBTSxDQUFDO1FBQUMsSUFBSSxFQUFFLE1BQU0sQ0FBQTtLQUFFLENBQUMsQ0EyQnJGO0lBRU0sVUFBVSxJQUFJLE1BQU0sQ0FFMUI7SUFFWSxpQkFBaUIsQ0FBQyxPQUFPLEVBQUUsTUFBTSxHQUFHLE9BQU8sQ0FBQyxRQUFRLEVBQUUsR0FBRyxTQUFTLENBQUMsQ0E0Qi9FO0lBRVksV0FBVyxDQUFDLEVBQUUsRUFBRSxNQUFNLEdBQUcsT0FBTyxDQUFDLE1BQU0sR0FBRyxTQUFTLENBQUMsQ0EwQmhFO0NBQ0YifQ==
|
|
@@ -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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA+BvC,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,138 @@
|
|
|
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
|
+
})))));
|
|
27
|
+
// Response from https://api.blobscan.com/blocks?sort=desc&type=canonical
|
|
28
|
+
export const BlobscanBlocksResponseSchema = z.object({
|
|
29
|
+
blocks: z.array(z.object({
|
|
30
|
+
hash: z.string(),
|
|
31
|
+
slot: z.number().int(),
|
|
32
|
+
number: z.number().int()
|
|
33
|
+
}))
|
|
34
|
+
});
|
|
35
|
+
export class BlobscanArchiveClient {
|
|
36
|
+
logger = createLogger('blob-client:blobscan-archive-client');
|
|
37
|
+
fetchOpts = {
|
|
38
|
+
headers: {
|
|
39
|
+
accept: 'application/json'
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
fetch = async (...args)=>{
|
|
43
|
+
return await retry(()=>fetch(...args), // eslint-disable-next-line @typescript-eslint/no-base-to-string
|
|
44
|
+
`Fetching ${args[0]}`, makeBackoff([
|
|
45
|
+
1,
|
|
46
|
+
1,
|
|
47
|
+
3
|
|
48
|
+
]), this.logger, /*failSilently=*/ false);
|
|
49
|
+
};
|
|
50
|
+
baseUrl;
|
|
51
|
+
instrumentation;
|
|
52
|
+
constructor(baseUrl, telemetry = getTelemetryClient()){
|
|
53
|
+
this.baseUrl = new URL(baseUrl);
|
|
54
|
+
if (this.baseUrl.protocol !== 'https:') {
|
|
55
|
+
throw new TypeError('BaseURL must be secure: ' + baseUrl);
|
|
56
|
+
}
|
|
57
|
+
this.instrumentation = new BlobArchiveClientInstrumentation(telemetry, this.baseUrl.origin, 'BlobscanArchiveClient');
|
|
58
|
+
}
|
|
59
|
+
async getLatestBlock() {
|
|
60
|
+
const url = new URL('blocks', this.baseUrl);
|
|
61
|
+
url.searchParams.set('sort', 'desc');
|
|
62
|
+
url.searchParams.set('type', 'canonical');
|
|
63
|
+
url.searchParams.set('p', '1');
|
|
64
|
+
url.searchParams.set('ps', '1');
|
|
65
|
+
this.logger.trace(`Fetching latest block from ${url.href}`);
|
|
66
|
+
const response = await this.fetch(url, this.fetchOpts);
|
|
67
|
+
if (response.status !== 200) {
|
|
68
|
+
throw new Error(`Failed to fetch latest block: ${response.statusText} (${response.status})`, {
|
|
69
|
+
cause: {
|
|
70
|
+
httpResponse: {
|
|
71
|
+
status: response.status,
|
|
72
|
+
body: await response.text().catch(()=>'Failed to read response body')
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
const parsed = await response.json().then((data)=>BlobscanBlocksResponseSchema.parse(data));
|
|
78
|
+
if (parsed.blocks.length === 0) {
|
|
79
|
+
throw new Error(`No blocks found at ${this.baseUrl}`);
|
|
80
|
+
}
|
|
81
|
+
return parsed.blocks[0];
|
|
82
|
+
}
|
|
83
|
+
getBaseUrl() {
|
|
84
|
+
return this.baseUrl.href;
|
|
85
|
+
}
|
|
86
|
+
async getBlobsFromBlock(blockId) {
|
|
87
|
+
const url = new URL(`blocks/${blockId}`, this.baseUrl);
|
|
88
|
+
url.searchParams.set('type', 'canonical');
|
|
89
|
+
url.searchParams.set('expand', 'blob,blob_data');
|
|
90
|
+
this.logger.trace(`Fetching blobs for block ${blockId} from ${url.href}`);
|
|
91
|
+
const response = await this.fetch(url, this.fetchOpts);
|
|
92
|
+
this.instrumentation.incRequest('blocks', response.status);
|
|
93
|
+
if (response.status === 404) {
|
|
94
|
+
this.logger.debug(`No blobs found for block ${blockId} at ${this.baseUrl}`);
|
|
95
|
+
return undefined;
|
|
96
|
+
} else if (response.status !== 200) {
|
|
97
|
+
throw new Error(`Failed to fetch blobs for block ${blockId}: ${response.statusText} (${response.status})`, {
|
|
98
|
+
cause: {
|
|
99
|
+
httpResponse: {
|
|
100
|
+
status: response.status,
|
|
101
|
+
body: await response.text().catch(()=>'Failed to read response body')
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
} else {
|
|
106
|
+
const result = await response.json().then((data)=>BlobscanBlockResponseSchema.parse(data));
|
|
107
|
+
this.logger.debug(`Fetched ${result.length} blobs for block ${blockId} from ${this.baseUrl}`);
|
|
108
|
+
this.instrumentation.incRetrievedBlobs(result.length);
|
|
109
|
+
return result;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
async getBlobData(id) {
|
|
113
|
+
const url = new URL(`blobs/${id}/data`, this.baseUrl);
|
|
114
|
+
const response = await this.fetch(url, this.fetchOpts);
|
|
115
|
+
this.instrumentation.incRequest('blobs', response.status);
|
|
116
|
+
if (response.status === 404) {
|
|
117
|
+
return undefined;
|
|
118
|
+
} else if (response.status !== 200) {
|
|
119
|
+
throw new Error(`Failed to fetch blob data for blob ${id}: ${response.statusText} (${response.status})`, {
|
|
120
|
+
cause: {
|
|
121
|
+
httpResponse: {
|
|
122
|
+
status: response.status,
|
|
123
|
+
body: await response.text().catch((err)=>{
|
|
124
|
+
this.logger.warn('Failed to read response body', err);
|
|
125
|
+
return '';
|
|
126
|
+
})
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
} else {
|
|
131
|
+
return await response.json().then((data)=>{
|
|
132
|
+
const blob = schemas.BufferHex.parse(data);
|
|
133
|
+
this.instrumentation.incRetrievedBlobs(1);
|
|
134
|
+
return blob;
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
@@ -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,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYmxvYl9zdG9yZV90ZXN0X3N1aXRlLmQudHMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvYmxvYnN0b3JlL2Jsb2Jfc3RvcmVfdGVzdF9zdWl0ZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFHQSxPQUFPLEtBQUssRUFBRSxTQUFTLEVBQUUsTUFBTSxnQkFBZ0IsQ0FBQztBQUVoRCx3QkFBZ0IsaUJBQWlCLENBQUMsWUFBWSxFQUFFLE1BQU0sT0FBTyxDQUFDLFNBQVMsQ0FBQyxRQXdHdkUifQ==
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"blob_store_test_suite.d.ts","sourceRoot":"","sources":["../../src/blobstore/blob_store_test_suite.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAEhD,wBAAgB,iBAAiB,CAAC,YAAY,EAAE,MAAM,OAAO,CAAC,SAAS,CAAC,QAwGvE"}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { Blob } from '@aztec/blob-lib';
|
|
2
|
+
import { Fr } from '@aztec/foundation/curves/bn254';
|
|
3
|
+
export function describeBlobStore(getBlobStore) {
|
|
4
|
+
let blobStore;
|
|
5
|
+
beforeEach(async ()=>{
|
|
6
|
+
blobStore = await getBlobStore();
|
|
7
|
+
});
|
|
8
|
+
it('should store and retrieve a blob by hash', async ()=>{
|
|
9
|
+
// Create a test blob with random fields
|
|
10
|
+
const testFields = [
|
|
11
|
+
Fr.random(),
|
|
12
|
+
Fr.random(),
|
|
13
|
+
Fr.random()
|
|
14
|
+
];
|
|
15
|
+
const blob = Blob.fromFields(testFields);
|
|
16
|
+
const blobHash = blob.getEthVersionedBlobHash();
|
|
17
|
+
// Store the blob
|
|
18
|
+
await blobStore.addBlobs([
|
|
19
|
+
blob
|
|
20
|
+
]);
|
|
21
|
+
// Retrieve the blob by hash
|
|
22
|
+
const retrievedBlobs = await blobStore.getBlobsByHashes([
|
|
23
|
+
blobHash
|
|
24
|
+
]);
|
|
25
|
+
// Verify the blob was retrieved and matches
|
|
26
|
+
expect(retrievedBlobs.length).toBe(1);
|
|
27
|
+
expect(retrievedBlobs[0]).toEqual(blob);
|
|
28
|
+
});
|
|
29
|
+
it('should handle multiple blobs stored and retrieved by their hashes', async ()=>{
|
|
30
|
+
// Create two different blobs
|
|
31
|
+
const blob1 = Blob.fromFields([
|
|
32
|
+
Fr.random(),
|
|
33
|
+
Fr.random()
|
|
34
|
+
]);
|
|
35
|
+
const blob2 = Blob.fromFields([
|
|
36
|
+
Fr.random(),
|
|
37
|
+
Fr.random(),
|
|
38
|
+
Fr.random()
|
|
39
|
+
]);
|
|
40
|
+
const blobHash1 = blob1.getEthVersionedBlobHash();
|
|
41
|
+
const blobHash2 = blob2.getEthVersionedBlobHash();
|
|
42
|
+
// Store both blobs
|
|
43
|
+
await blobStore.addBlobs([
|
|
44
|
+
blob1,
|
|
45
|
+
blob2
|
|
46
|
+
]);
|
|
47
|
+
// Retrieve and verify both blobs
|
|
48
|
+
const retrievedBlobs = await blobStore.getBlobsByHashes([
|
|
49
|
+
blobHash1,
|
|
50
|
+
blobHash2
|
|
51
|
+
]);
|
|
52
|
+
expect(retrievedBlobs.length).toBe(2);
|
|
53
|
+
expect(retrievedBlobs[0]).toEqual(blob1);
|
|
54
|
+
expect(retrievedBlobs[1]).toEqual(blob2);
|
|
55
|
+
});
|
|
56
|
+
it('should return empty array for non-existent blob hash', async ()=>{
|
|
57
|
+
// Create a random hash that doesn't exist
|
|
58
|
+
const nonExistentHash = Buffer.alloc(32);
|
|
59
|
+
nonExistentHash.fill(0xff);
|
|
60
|
+
const retrievedBlobs = await blobStore.getBlobsByHashes([
|
|
61
|
+
nonExistentHash
|
|
62
|
+
]);
|
|
63
|
+
expect(retrievedBlobs).toEqual([]);
|
|
64
|
+
});
|
|
65
|
+
it('should handle retrieving subset of stored blobs', async ()=>{
|
|
66
|
+
// Store multiple blobs
|
|
67
|
+
const blob1 = Blob.fromFields([
|
|
68
|
+
Fr.random()
|
|
69
|
+
]);
|
|
70
|
+
const blob2 = Blob.fromFields([
|
|
71
|
+
Fr.random()
|
|
72
|
+
]);
|
|
73
|
+
const blob3 = Blob.fromFields([
|
|
74
|
+
Fr.random()
|
|
75
|
+
]);
|
|
76
|
+
await blobStore.addBlobs([
|
|
77
|
+
blob1,
|
|
78
|
+
blob2,
|
|
79
|
+
blob3
|
|
80
|
+
]);
|
|
81
|
+
// Retrieve only some of them
|
|
82
|
+
const blobHash1 = blob1.getEthVersionedBlobHash();
|
|
83
|
+
const blobHash3 = blob3.getEthVersionedBlobHash();
|
|
84
|
+
const retrievedBlobs = await blobStore.getBlobsByHashes([
|
|
85
|
+
blobHash1,
|
|
86
|
+
blobHash3
|
|
87
|
+
]);
|
|
88
|
+
expect(retrievedBlobs.length).toBe(2);
|
|
89
|
+
expect(retrievedBlobs[0]).toEqual(blob1);
|
|
90
|
+
expect(retrievedBlobs[1]).toEqual(blob3);
|
|
91
|
+
});
|
|
92
|
+
it('should handle duplicate blob hashes in request', async ()=>{
|
|
93
|
+
const blob = Blob.fromFields([
|
|
94
|
+
Fr.random()
|
|
95
|
+
]);
|
|
96
|
+
const blobHash = blob.getEthVersionedBlobHash();
|
|
97
|
+
await blobStore.addBlobs([
|
|
98
|
+
blob
|
|
99
|
+
]);
|
|
100
|
+
// Request the same blob hash multiple times
|
|
101
|
+
const retrievedBlobs = await blobStore.getBlobsByHashes([
|
|
102
|
+
blobHash,
|
|
103
|
+
blobHash
|
|
104
|
+
]);
|
|
105
|
+
// Implementation may return duplicates or deduplicate - both are valid
|
|
106
|
+
expect(retrievedBlobs.length).toBeGreaterThanOrEqual(1);
|
|
107
|
+
expect(retrievedBlobs[0]).toEqual(blob);
|
|
108
|
+
});
|
|
109
|
+
it('should overwrite blob when storing with same hash', async ()=>{
|
|
110
|
+
// Create two blobs that will have the same hash (same content)
|
|
111
|
+
const fields = [
|
|
112
|
+
Fr.random(),
|
|
113
|
+
Fr.random()
|
|
114
|
+
];
|
|
115
|
+
const blob1 = Blob.fromFields(fields);
|
|
116
|
+
const blob2 = Blob.fromFields(fields);
|
|
117
|
+
const blobHash = blob1.getEthVersionedBlobHash();
|
|
118
|
+
// Store first blob
|
|
119
|
+
await blobStore.addBlobs([
|
|
120
|
+
blob1
|
|
121
|
+
]);
|
|
122
|
+
// Overwrite with second blob (same hash)
|
|
123
|
+
await blobStore.addBlobs([
|
|
124
|
+
blob2
|
|
125
|
+
]);
|
|
126
|
+
// Retrieve and verify it exists
|
|
127
|
+
const retrievedBlobs = await blobStore.getBlobsByHashes([
|
|
128
|
+
blobHash
|
|
129
|
+
]);
|
|
130
|
+
expect(retrievedBlobs.length).toBe(1);
|
|
131
|
+
expect(retrievedBlobs[0]).toEqual(blob1); // Same content
|
|
132
|
+
});
|
|
133
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export * from './memory_blob_store.js';
|
|
2
|
+
export * from './interface.js';
|
|
3
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguZC50cyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9ibG9ic3RvcmUvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsY0FBYyx3QkFBd0IsQ0FBQztBQUN2QyxjQUFjLGdCQUFnQixDQUFDIn0=
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/blobstore/index.ts"],"names":[],"mappings":"AAAA,cAAc,wBAAwB,CAAC;AACvC,cAAc,gBAAgB,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Blob } from '@aztec/blob-lib';
|
|
2
|
+
export interface BlobStore {
|
|
3
|
+
/**
|
|
4
|
+
* Get blobs by their hashes
|
|
5
|
+
*/
|
|
6
|
+
getBlobsByHashes: (blobHashes: Buffer[]) => Promise<Blob[]>;
|
|
7
|
+
/**
|
|
8
|
+
* Add blobs to the store, indexed by their hashes
|
|
9
|
+
*/
|
|
10
|
+
addBlobs: (blobs: Blob[]) => Promise<void>;
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW50ZXJmYWNlLmQudHMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvYmxvYnN0b3JlL2ludGVyZmFjZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssRUFBRSxJQUFJLEVBQUUsTUFBTSxpQkFBaUIsQ0FBQztBQUU1QyxNQUFNLFdBQVcsU0FBUztJQUN4Qjs7T0FFRztJQUNILGdCQUFnQixFQUFFLENBQUMsVUFBVSxFQUFFLE1BQU0sRUFBRSxLQUFLLE9BQU8sQ0FBQyxJQUFJLEVBQUUsQ0FBQyxDQUFDO0lBQzVEOztPQUVHO0lBQ0gsUUFBUSxFQUFFLENBQUMsS0FBSyxFQUFFLElBQUksRUFBRSxLQUFLLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FBQztDQUM1QyJ9
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"interface.d.ts","sourceRoot":"","sources":["../../src/blobstore/interface.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AAE5C,MAAM,WAAW,SAAS;IACxB;;OAEG;IACH,gBAAgB,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC;IAC5D;;OAEG;IACH,QAAQ,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5C"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|