@shelby-protocol/sdk 0.0.1-experimental.0
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 +60 -0
- package/dist/browser/index.d.ts +10 -0
- package/dist/browser/index.mjs +55 -0
- package/dist/chunk-7P6ASYW6.mjs +9 -0
- package/dist/chunk-A4IG6GSE.mjs +21 -0
- package/dist/chunk-D2FERD4A.mjs +39 -0
- package/dist/chunk-DBTBUKCW.mjs +235 -0
- package/dist/chunk-E3QOKRQ4.mjs +50 -0
- package/dist/chunk-EFE5Y7IE.mjs +331 -0
- package/dist/chunk-GGYTHP5F.mjs +84 -0
- package/dist/chunk-I6NG5GNL.mjs +8 -0
- package/dist/chunk-IHTPXUYI.mjs +0 -0
- package/dist/chunk-MWDW4ROU.mjs +0 -0
- package/dist/chunk-NPM7WXK3.mjs +49 -0
- package/dist/chunk-OWIIKLFI.mjs +43 -0
- package/dist/chunk-PZPMURE4.mjs +0 -0
- package/dist/chunk-QEMIORTL.mjs +9 -0
- package/dist/chunk-VLLCOFH7.mjs +73 -0
- package/dist/chunk-WVWL2OPB.mjs +0 -0
- package/dist/chunk-XZMYTH4K.mjs +90 -0
- package/dist/chunk-YV67F5NY.mjs +38 -0
- package/dist/chunk-ZHXCVRZX.mjs +0 -0
- package/dist/core/ShelbyClient.d.ts +34 -0
- package/dist/core/ShelbyClient.mjs +8 -0
- package/dist/core/blobs.d.ts +52 -0
- package/dist/core/blobs.mjs +7 -0
- package/dist/core/commitments.d.ts +102 -0
- package/dist/core/commitments.mjs +11 -0
- package/dist/core/constants.d.ts +26 -0
- package/dist/core/constants.mjs +27 -0
- package/dist/core/index.d.ts +10 -0
- package/dist/core/index.mjs +55 -0
- package/dist/core/layout.d.ts +41 -0
- package/dist/core/layout.mjs +14 -0
- package/dist/core/promises.d.ts +10 -0
- package/dist/core/promises.mjs +7 -0
- package/dist/core/types/blobs.d.ts +132 -0
- package/dist/core/types/blobs.mjs +1 -0
- package/dist/core/types/encodings.d.ts +19 -0
- package/dist/core/types/encodings.mjs +1 -0
- package/dist/core/types/index.d.ts +5 -0
- package/dist/core/types/index.mjs +3 -0
- package/dist/node/clients/ShelbyBlobClient.d.ts +215 -0
- package/dist/node/clients/ShelbyBlobClient.mjs +19 -0
- package/dist/node/clients/ShelbyNodeClient.d.ts +95 -0
- package/dist/node/clients/ShelbyNodeClient.mjs +22 -0
- package/dist/node/clients/ShelbyRPCClient.d.ts +72 -0
- package/dist/node/clients/ShelbyRPCClient.mjs +17 -0
- package/dist/node/clients/index.d.ts +12 -0
- package/dist/node/clients/index.mjs +29 -0
- package/dist/node/commitments.d.ts +35 -0
- package/dist/node/commitments.mjs +18 -0
- package/dist/node/erasure.d.ts +12 -0
- package/dist/node/erasure.mjs +7 -0
- package/dist/node/index.d.ts +18 -0
- package/dist/node/index.mjs +85 -0
- package/dist/node/readableUtil.d.ts +2 -0
- package/dist/node/readableUtil.mjs +9 -0
- package/dist/node/testUtil.d.ts +1 -0
- package/dist/node/testUtil.mjs +7 -0
- package/dist/readableUtil-BW_7enT-.d.ts +12 -0
- package/dist/testUtil-BnxAchIN.d.ts +8 -0
- package/package.json +60 -0
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
import {
|
|
2
|
+
generateCommitments
|
|
3
|
+
} from "./chunk-VLLCOFH7.mjs";
|
|
4
|
+
import {
|
|
5
|
+
createBlobKey
|
|
6
|
+
} from "./chunk-QEMIORTL.mjs";
|
|
7
|
+
import {
|
|
8
|
+
SHELBY_DEPLOYER
|
|
9
|
+
} from "./chunk-D2FERD4A.mjs";
|
|
10
|
+
|
|
11
|
+
// src/node/clients/ShelbyBlobClient.ts
|
|
12
|
+
import { Readable } from "stream";
|
|
13
|
+
import {
|
|
14
|
+
AccountAddress,
|
|
15
|
+
Aptos,
|
|
16
|
+
Hex,
|
|
17
|
+
MoveVector
|
|
18
|
+
} from "@aptos-labs/ts-sdk";
|
|
19
|
+
var ShelbyBlobClient = class _ShelbyBlobClient {
|
|
20
|
+
aptos;
|
|
21
|
+
deployer;
|
|
22
|
+
/**
|
|
23
|
+
* The ShelbyBlobClient is used to interact with the Shelby contract on the Aptos blockchain. This
|
|
24
|
+
* includes functions like writing blob commitments, confirming chunks, and retrieving blob metadata.
|
|
25
|
+
*
|
|
26
|
+
* @param config.aptos.config - The Aptos config.
|
|
27
|
+
* @param config.shelbyDeployer - The deployer account address of the Shelby contract. If not provided, the default deployer address will be used.
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```typescript
|
|
31
|
+
* const aptos = new Aptos(new AptosConfig({ network: Network.TESTNET }));
|
|
32
|
+
* const blobClient = new ShelbyBlobClient({ aptos });
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
constructor(config) {
|
|
36
|
+
this.aptos = new Aptos(config.aptos.config);
|
|
37
|
+
this.deployer = config.shelbyDeployer ?? AccountAddress.fromString(SHELBY_DEPLOYER);
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Retrieves the blob metadata from the blockchain. If it does not exist,
|
|
41
|
+
* returns `undefined`.
|
|
42
|
+
*
|
|
43
|
+
* @param params.account - The account namespace the blob is stored in (e.g. "0x1")
|
|
44
|
+
* @param params.name - The name of the blob (e.g. "foo/bar")
|
|
45
|
+
* @returns The blob metadata.
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* ```typescript
|
|
49
|
+
* const metadata = await client.getBlobMetadata({
|
|
50
|
+
* account: AccountAddress.fromString("0x1"),
|
|
51
|
+
* name: "foo/bar.txt",
|
|
52
|
+
* });
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
async getBlobMetadata(params) {
|
|
56
|
+
try {
|
|
57
|
+
const rawMetadata = await this.aptos.view({
|
|
58
|
+
payload: {
|
|
59
|
+
function: `${this.deployer.toString()}::prototype_interface::get_metadata`,
|
|
60
|
+
functionArguments: [
|
|
61
|
+
createBlobKey({
|
|
62
|
+
account: params.account,
|
|
63
|
+
blobName: params.name
|
|
64
|
+
})
|
|
65
|
+
]
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
const metadata = rawMetadata[0];
|
|
69
|
+
return {
|
|
70
|
+
blobMerkleRoot: Hex.fromHexInput(
|
|
71
|
+
metadata.blob_merkle_root
|
|
72
|
+
).toUint8Array(),
|
|
73
|
+
owner: AccountAddress.fromString(metadata.owner),
|
|
74
|
+
name: metadata.name,
|
|
75
|
+
size: metadata.size,
|
|
76
|
+
encoding: {
|
|
77
|
+
variant: "clay",
|
|
78
|
+
erasureK: metadata.encoding.num_data_chunks,
|
|
79
|
+
erasureM: metadata.encoding.num_parity_chunks,
|
|
80
|
+
chunksetSize: metadata.encoding.chunkset_size
|
|
81
|
+
},
|
|
82
|
+
expirationMicros: metadata.expiration_micros
|
|
83
|
+
};
|
|
84
|
+
} catch (error) {
|
|
85
|
+
if (error instanceof Error && // Depending on the network, the error message may show up differently.
|
|
86
|
+
(error.message?.includes("sub_status: Some(404)") || error.message?.includes("EBLOB_NOT_FOUND"))) {
|
|
87
|
+
return void 0;
|
|
88
|
+
}
|
|
89
|
+
throw error;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Retrieves all the blobs and their metadata for an account from the
|
|
94
|
+
* blockchain.
|
|
95
|
+
*
|
|
96
|
+
* @param params.account - The account namespace the blobs are stored in (e.g. "0x1")
|
|
97
|
+
* @returns The blob metadata for all the blobs for the account.
|
|
98
|
+
*
|
|
99
|
+
* @example
|
|
100
|
+
* ```typescript
|
|
101
|
+
* // BlobMetadata[]
|
|
102
|
+
* const blobs = await client.getAccountBlobs({
|
|
103
|
+
* account: AccountAddress.fromString("0x1"),
|
|
104
|
+
* });
|
|
105
|
+
* ```
|
|
106
|
+
*/
|
|
107
|
+
async getAccountBlobs(params) {
|
|
108
|
+
const rawBlobMetadatas = await this.aptos.view({
|
|
109
|
+
payload: {
|
|
110
|
+
function: `${this.deployer.toString()}::prototype_interface::get_blobs_for_owner`,
|
|
111
|
+
functionArguments: [params.account.toString()]
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
return rawBlobMetadatas[0].map((blob) => ({
|
|
115
|
+
blobMerkleRoot: Hex.fromHexInput(blob.blob_merkle_root).toUint8Array(),
|
|
116
|
+
owner: AccountAddress.fromString(blob.owner),
|
|
117
|
+
name: blob.name,
|
|
118
|
+
size: blob.size,
|
|
119
|
+
encoding: {
|
|
120
|
+
variant: "clay",
|
|
121
|
+
erasureK: blob.encoding.num_data_chunks,
|
|
122
|
+
erasureM: blob.encoding.num_parity_chunks,
|
|
123
|
+
chunksetSize: blob.encoding.chunkset_size
|
|
124
|
+
},
|
|
125
|
+
expirationMicros: blob.expiration_micros
|
|
126
|
+
}));
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Retrieves the blob chunks for a given blob from the blockchain. The blob chunk will contain
|
|
130
|
+
* the commitment, the storage provider location, and the status of the chunk (stored or pending).
|
|
131
|
+
*
|
|
132
|
+
* @param params.account - The account namespace the blob is stored in (e.g. "0x1")
|
|
133
|
+
* @param params.name - The name of the blob (e.g. "foo/bar")
|
|
134
|
+
* @returns The chunks that make up the blob.
|
|
135
|
+
*
|
|
136
|
+
* @example
|
|
137
|
+
* ```typescript
|
|
138
|
+
* // BlobChunk[]
|
|
139
|
+
* const chunks = await client.getBlobChunks({
|
|
140
|
+
* account: AccountAddress.fromString("0x1"),
|
|
141
|
+
* name: "foo/bar.txt",
|
|
142
|
+
* });
|
|
143
|
+
*
|
|
144
|
+
* const isStored = chunks.every((c) => c.location.variant === "stored");
|
|
145
|
+
* ```
|
|
146
|
+
*/
|
|
147
|
+
async getBlobChunks(params) {
|
|
148
|
+
const chunks = await this.aptos.view({
|
|
149
|
+
payload: {
|
|
150
|
+
function: `${this.deployer.toString()}::prototype_interface::get_chunks`,
|
|
151
|
+
functionArguments: [
|
|
152
|
+
createBlobKey({
|
|
153
|
+
account: params.account,
|
|
154
|
+
blobName: params.name
|
|
155
|
+
})
|
|
156
|
+
]
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
return chunks[0].map((c) => ({
|
|
160
|
+
commitment: Hex.fromHexInput(c.commitment).toUint8Array(),
|
|
161
|
+
location: {
|
|
162
|
+
variant: c.location.__variant__ === "Stored" ? "stored" : "pending",
|
|
163
|
+
provider: AccountAddress.fromString(c.location.provider)
|
|
164
|
+
}
|
|
165
|
+
}));
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Writes the blob commitments to the blockchain.
|
|
169
|
+
*
|
|
170
|
+
* If `data` is provided instead of `blobCommitments`, the data will be encoded into commitments before being written
|
|
171
|
+
* to the blockchain.
|
|
172
|
+
*
|
|
173
|
+
* @param params.signer - The account that is signing the transaction.
|
|
174
|
+
* @param params.blobName - The name of the blob (e.g. "foo/bar")
|
|
175
|
+
* @param params.expirationMicros - The expiration time of the blob in microseconds.
|
|
176
|
+
* @param params.options - Additional options for transaction building and encoding.
|
|
177
|
+
* @param params.blobCommitments - The blob commitments to write to the blockchain (required if `data` is not provided).
|
|
178
|
+
* @param params.data - The data to encode into commitments before writing to the blockchain (required if `blobCommitments` is not provided).
|
|
179
|
+
*
|
|
180
|
+
* @returns The blob commitments and the pending transaction.
|
|
181
|
+
*
|
|
182
|
+
* @example
|
|
183
|
+
* ```typescript
|
|
184
|
+
* const blobCommitments = await generateCommitments(Readable.from(data));
|
|
185
|
+
*
|
|
186
|
+
* const { blobCommitments, transaction } = await client.writeBlobCommitments({
|
|
187
|
+
* signer,
|
|
188
|
+
* blobName: "foo/bar.txt",
|
|
189
|
+
* blobCommitments,
|
|
190
|
+
* expirationMicros: Date.now() * 1000 + 3600_000_000, // 1 hour from now in microseconds
|
|
191
|
+
* });
|
|
192
|
+
* ```
|
|
193
|
+
*/
|
|
194
|
+
async writeBlobCommitments(params) {
|
|
195
|
+
let blobCommitments;
|
|
196
|
+
if ("blobCommitments" in params) {
|
|
197
|
+
blobCommitments = params.blobCommitments;
|
|
198
|
+
} else {
|
|
199
|
+
blobCommitments = await generateCommitments(
|
|
200
|
+
Readable.from(params.blobData),
|
|
201
|
+
void 0,
|
|
202
|
+
params.options?.encoding
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
const transaction = await this.aptos.transaction.build.simple({
|
|
206
|
+
...params.options?.build,
|
|
207
|
+
data: _ShelbyBlobClient.createWriteBlobCommitmentsPayload({
|
|
208
|
+
deployer: this.deployer,
|
|
209
|
+
account: params.signer.accountAddress,
|
|
210
|
+
blobName: params.blobName,
|
|
211
|
+
blobMerkleRoot: blobCommitments.blob_merkle_root,
|
|
212
|
+
chunksetChunkCommitments: blobCommitments.chunkset_commitments.map(
|
|
213
|
+
(chunkset) => chunkset.chunk_commitments
|
|
214
|
+
),
|
|
215
|
+
size: blobCommitments.raw_data_size,
|
|
216
|
+
expirationMicros: params.expirationMicros
|
|
217
|
+
}),
|
|
218
|
+
sender: params.signer.accountAddress
|
|
219
|
+
});
|
|
220
|
+
return {
|
|
221
|
+
blobCommitments,
|
|
222
|
+
transaction: await this.aptos.signAndSubmitTransaction({
|
|
223
|
+
signer: params.signer,
|
|
224
|
+
transaction
|
|
225
|
+
})
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Confirms an array of signed chunk commitments for a blob. Once all chunks for a blob are confirmed, the blob is
|
|
230
|
+
* considered "stored" and can be retrieved by RPC nodes.
|
|
231
|
+
*
|
|
232
|
+
* @param params.signer - The account that is signing the transaction.
|
|
233
|
+
* @param params.account - The account namespace the blob is stored in (e.g. "0x1")
|
|
234
|
+
* @param params.blobName - The name of the blob (e.g. "foo/bar")
|
|
235
|
+
* @param params.signedChunksetChunkCommitments - The signed chunk commitments, signed by their storage provider.
|
|
236
|
+
* @param params.options - Additional options for transaction building and encoding.
|
|
237
|
+
*
|
|
238
|
+
* @returns The pending transaction response.
|
|
239
|
+
*
|
|
240
|
+
* @example
|
|
241
|
+
* ```typescript
|
|
242
|
+
* const { transaction } = await client.confirmBlobChunks({
|
|
243
|
+
* signer,
|
|
244
|
+
* account: AccountAddress.fromString("0x1"),
|
|
245
|
+
* blobName: "foo/bar.txt",
|
|
246
|
+
* signedChunksetChunkCommitments,
|
|
247
|
+
* });
|
|
248
|
+
* ```
|
|
249
|
+
*/
|
|
250
|
+
async confirmBlobChunks(params) {
|
|
251
|
+
const transaction = await this.aptos.transaction.build.simple({
|
|
252
|
+
...params.options?.build,
|
|
253
|
+
data: _ShelbyBlobClient.createConfirmBlobChunksPayload({
|
|
254
|
+
deployer: this.deployer,
|
|
255
|
+
account: params.account,
|
|
256
|
+
blobName: params.blobName,
|
|
257
|
+
signedChunksetChunkCommitments: params.signedChunksetChunkCommitments
|
|
258
|
+
}),
|
|
259
|
+
sender: params.signer.accountAddress
|
|
260
|
+
});
|
|
261
|
+
return {
|
|
262
|
+
transaction: await this.aptos.signAndSubmitTransaction({
|
|
263
|
+
signer: params.signer,
|
|
264
|
+
transaction
|
|
265
|
+
})
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Creates a transaction payload to write blob commitments to the blockchain.
|
|
270
|
+
*
|
|
271
|
+
* @param params.deployer - The deployer account address of the Shelby contract. If not provided, the default deployer address will be used.
|
|
272
|
+
* @param params.account - The account namespace the blob is stored in (e.g. "0x1")
|
|
273
|
+
* @param params.blobName - The name of the blob (e.g. "foo/bar")
|
|
274
|
+
* @param params.blobMerkleRoot - The blob merkle root.
|
|
275
|
+
* @param params.chunksetChunkCommitments - The chunk commitments for each chunkset.
|
|
276
|
+
* @param params.expirationMicros - The expiration time of the blob in microseconds.
|
|
277
|
+
* @param params.size - The size of the blob in bytes.
|
|
278
|
+
*
|
|
279
|
+
* @see https://github.com/JumpCrypto/shelby/blob/e009607aad330ccddb08d80bf9addfaadae7972b/move/prototype/move/sources/prototype_interface.move#L211-L220
|
|
280
|
+
*/
|
|
281
|
+
static createWriteBlobCommitmentsPayload(params) {
|
|
282
|
+
return {
|
|
283
|
+
function: `${(params.deployer ?? SHELBY_DEPLOYER).toString()}::prototype_interface::write_commitments`,
|
|
284
|
+
functionArguments: [
|
|
285
|
+
createBlobKey({
|
|
286
|
+
account: params.account,
|
|
287
|
+
blobName: params.blobName
|
|
288
|
+
}),
|
|
289
|
+
params.size,
|
|
290
|
+
MoveVector.U8(params.blobMerkleRoot),
|
|
291
|
+
params.chunksetChunkCommitments.map(
|
|
292
|
+
(chunkset) => chunkset.map((chunkCommitment) => MoveVector.U8(chunkCommitment))
|
|
293
|
+
),
|
|
294
|
+
params.expirationMicros,
|
|
295
|
+
true
|
|
296
|
+
]
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Creates a transaction payload to confirm the chunks of a blob.
|
|
301
|
+
*
|
|
302
|
+
* @param params.deployer - The deployer account address of the Shelby contract. If not provided, the default deployer address will be used.
|
|
303
|
+
* @param params.account - The account namespace the blob is stored in (e.g. "0x1")
|
|
304
|
+
* @param params.blobName - The name of the blob (e.g. "foo/bar")
|
|
305
|
+
* @param params.signedChunksetChunkCommitments - The signed chunk commitments, signed by their storage provider.
|
|
306
|
+
*
|
|
307
|
+
* @see https://github.com/JumpCrypto/shelby/blob/e009607aad330ccddb08d80bf9addfaadae7972b/move/prototype/move/sources/prototype_interface.move#L343-L349
|
|
308
|
+
*/
|
|
309
|
+
static createConfirmBlobChunksPayload(params) {
|
|
310
|
+
return {
|
|
311
|
+
function: `${(params.deployer ?? SHELBY_DEPLOYER).toString()}::prototype_interface::write_complete`,
|
|
312
|
+
functionArguments: [
|
|
313
|
+
createBlobKey({
|
|
314
|
+
account: params.account,
|
|
315
|
+
blobName: params.blobName
|
|
316
|
+
}),
|
|
317
|
+
params.signedChunksetChunkCommitments.map(
|
|
318
|
+
(chunkset) => chunkset.map((chunk) => AccountAddress.from(chunk.provider))
|
|
319
|
+
),
|
|
320
|
+
params.signedChunksetChunkCommitments.map(
|
|
321
|
+
(chunkset) => chunkset.map((chunk) => MoveVector.U8(chunk.signedCommitment))
|
|
322
|
+
),
|
|
323
|
+
true
|
|
324
|
+
]
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
export {
|
|
330
|
+
ShelbyBlobClient
|
|
331
|
+
};
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ERASURE_K,
|
|
3
|
+
ERASURE_M,
|
|
4
|
+
getChunkSizeBytes,
|
|
5
|
+
getChunksetSizeBytes
|
|
6
|
+
} from "./chunk-D2FERD4A.mjs";
|
|
7
|
+
|
|
8
|
+
// src/core/layout.ts
|
|
9
|
+
import { AccountAddress } from "@aptos-labs/ts-sdk";
|
|
10
|
+
import { z } from "zod";
|
|
11
|
+
var BlobNameSchema = z.string().min(1, "Blob name path parameter cannot be empty.").max(1024, "Blob name cannot exceed 1024 characters.").refine((name) => !name.endsWith("/"), {
|
|
12
|
+
message: "Blob name cannot end with a slash"
|
|
13
|
+
});
|
|
14
|
+
var ChunkKey = class _ChunkKey {
|
|
15
|
+
constructor(account, blobName, chunksetIdx, chunkIdx) {
|
|
16
|
+
this.account = account;
|
|
17
|
+
this.blobName = blobName;
|
|
18
|
+
this.chunksetIdx = chunksetIdx;
|
|
19
|
+
this.chunkIdx = chunkIdx;
|
|
20
|
+
const N = ERASURE_K + ERASURE_M;
|
|
21
|
+
if (chunkIdx >= N) {
|
|
22
|
+
throw new Error(`Cannot create a chunk with idx ${chunkIdx}. M+K=${N}`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
key() {
|
|
26
|
+
return `${this.account.toString()}.${this.blobName}.${this.chunksetIdx}.${this.chunkIdx}`;
|
|
27
|
+
}
|
|
28
|
+
// Returns the range in the blob that this key represents, or "partity" if this is parity chunk.
|
|
29
|
+
range() {
|
|
30
|
+
if (this.chunkIdx >= ERASURE_K) {
|
|
31
|
+
return "parity";
|
|
32
|
+
}
|
|
33
|
+
const chunksetStart = this.chunksetIdx * getChunksetSizeBytes();
|
|
34
|
+
const chunkStart = chunksetStart + this.chunkIdx * getChunkSizeBytes();
|
|
35
|
+
return {
|
|
36
|
+
start: chunkStart,
|
|
37
|
+
end: chunkStart + getChunkSizeBytes()
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
static fromJSON(json) {
|
|
41
|
+
return new _ChunkKey(
|
|
42
|
+
AccountAddress.fromString(json.account),
|
|
43
|
+
json.blobName,
|
|
44
|
+
json.chunksetIdx,
|
|
45
|
+
json.chunkIdx
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
toJSON() {
|
|
49
|
+
return {
|
|
50
|
+
account: this.account.toString(),
|
|
51
|
+
blobName: this.blobName,
|
|
52
|
+
chunksetIdx: this.chunksetIdx,
|
|
53
|
+
chunkIdx: this.chunkIdx
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
function roundSize(size) {
|
|
58
|
+
let ret = 0;
|
|
59
|
+
let remain = size;
|
|
60
|
+
while (remain > 0) {
|
|
61
|
+
ret += getChunksetSizeBytes();
|
|
62
|
+
remain -= getChunksetSizeBytes();
|
|
63
|
+
}
|
|
64
|
+
return ret;
|
|
65
|
+
}
|
|
66
|
+
function allChunksForBlob(account, blobName, _size) {
|
|
67
|
+
const ret = [];
|
|
68
|
+
const size = roundSize(_size);
|
|
69
|
+
const nChunksets = size / getChunksetSizeBytes();
|
|
70
|
+
for (let chunksetIdx = 0; chunksetIdx < nChunksets; ++chunksetIdx) {
|
|
71
|
+
const n = ERASURE_K + ERASURE_M;
|
|
72
|
+
for (let chunkIdx = 0; chunkIdx < n; ++chunkIdx) {
|
|
73
|
+
ret.push(new ChunkKey(account, blobName, chunksetIdx, chunkIdx));
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return ret;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export {
|
|
80
|
+
BlobNameSchema,
|
|
81
|
+
ChunkKey,
|
|
82
|
+
roundSize,
|
|
83
|
+
allChunksForBlob
|
|
84
|
+
};
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import {
|
|
2
|
+
roundSize
|
|
3
|
+
} from "./chunk-GGYTHP5F.mjs";
|
|
4
|
+
import {
|
|
5
|
+
ERASURE_K,
|
|
6
|
+
ERASURE_M,
|
|
7
|
+
getChunksetSizeBytes
|
|
8
|
+
} from "./chunk-D2FERD4A.mjs";
|
|
9
|
+
|
|
10
|
+
// src/core/commitments.ts
|
|
11
|
+
import { z } from "zod";
|
|
12
|
+
var ChunksetCommitmentSchema = z.object({
|
|
13
|
+
// Chunkset root (vector commitment of child chunks)
|
|
14
|
+
chunkset_root: z.string().nullable(),
|
|
15
|
+
// the size is known statically from the current configuration
|
|
16
|
+
chunk_commitments: z.array(z.string())
|
|
17
|
+
}).refine(
|
|
18
|
+
(data) => {
|
|
19
|
+
return data.chunk_commitments.length === ERASURE_K + ERASURE_M;
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
message: `Chunkset must have exactly ${ERASURE_K + ERASURE_M} chunks (ERASURE_K + ERASURE_M = ${ERASURE_K} + ${ERASURE_M})`,
|
|
23
|
+
path: ["chunk_commitments"]
|
|
24
|
+
}
|
|
25
|
+
);
|
|
26
|
+
function expectedTotalChunksets(rawSize) {
|
|
27
|
+
return roundSize(rawSize) / getChunksetSizeBytes();
|
|
28
|
+
}
|
|
29
|
+
var BlobCommitmentsSchema = z.object({
|
|
30
|
+
schema_version: z.string(),
|
|
31
|
+
raw_data_size: z.number(),
|
|
32
|
+
// FIXME I am not sure about this being here, or if it should be somewhere else
|
|
33
|
+
blob_merkle_root: z.string(),
|
|
34
|
+
chunkset_commitments: z.array(ChunksetCommitmentSchema)
|
|
35
|
+
}).refine(
|
|
36
|
+
(data) => {
|
|
37
|
+
return expectedTotalChunksets(data.raw_data_size) === data.chunkset_commitments.length;
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
message: "Total chunkset count mismatches with raw data size",
|
|
41
|
+
// FIXME put more details in here
|
|
42
|
+
path: ["chunkset_commitments"]
|
|
43
|
+
}
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
export {
|
|
47
|
+
ChunksetCommitmentSchema,
|
|
48
|
+
BlobCommitmentsSchema
|
|
49
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// src/node/erasure.ts
|
|
2
|
+
import { Buffer } from "buffer";
|
|
3
|
+
import { RS } from "@shelby-protocol/reed-solomon";
|
|
4
|
+
async function erasureEncode(data, k, m) {
|
|
5
|
+
const rawShardSize = Math.ceil(data.length / k);
|
|
6
|
+
const shardSize = Math.ceil(rawShardSize / 8) * 8;
|
|
7
|
+
const dataSize = shardSize * k;
|
|
8
|
+
const paritySize = shardSize * m;
|
|
9
|
+
const dataBuf = Buffer.alloc(dataSize);
|
|
10
|
+
data.copy(dataBuf, 0);
|
|
11
|
+
const parityBuf = Buffer.alloc(paritySize);
|
|
12
|
+
let sources = 0;
|
|
13
|
+
for (let i = 0; i < k; i++) sources |= 1 << i;
|
|
14
|
+
let targets = 0;
|
|
15
|
+
for (let i = k; i < k + m; i++) targets |= 1 << i;
|
|
16
|
+
const context = RS.create(k, m);
|
|
17
|
+
await new Promise((resolve, reject) => {
|
|
18
|
+
RS.encode(
|
|
19
|
+
context,
|
|
20
|
+
sources,
|
|
21
|
+
targets,
|
|
22
|
+
dataBuf,
|
|
23
|
+
0,
|
|
24
|
+
dataSize,
|
|
25
|
+
parityBuf,
|
|
26
|
+
0,
|
|
27
|
+
paritySize,
|
|
28
|
+
(err) => err ? reject(err) : resolve()
|
|
29
|
+
);
|
|
30
|
+
});
|
|
31
|
+
const shards = [];
|
|
32
|
+
for (let i = 0; i < k; i++) {
|
|
33
|
+
shards.push(dataBuf.slice(i * shardSize, i * shardSize + shardSize));
|
|
34
|
+
}
|
|
35
|
+
for (let j = 0; j < m; j++) {
|
|
36
|
+
shards.push(parityBuf.slice(j * shardSize, j * shardSize + shardSize));
|
|
37
|
+
}
|
|
38
|
+
return shards;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export {
|
|
42
|
+
erasureEncode
|
|
43
|
+
};
|
|
File without changes
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import {
|
|
2
|
+
erasureEncode
|
|
3
|
+
} from "./chunk-OWIIKLFI.mjs";
|
|
4
|
+
import {
|
|
5
|
+
readInChunks,
|
|
6
|
+
zeroPadBuffer
|
|
7
|
+
} from "./chunk-E3QOKRQ4.mjs";
|
|
8
|
+
import {
|
|
9
|
+
ERASURE_K,
|
|
10
|
+
ERASURE_M,
|
|
11
|
+
getChunksetSizeBytes
|
|
12
|
+
} from "./chunk-D2FERD4A.mjs";
|
|
13
|
+
|
|
14
|
+
// src/node/commitments.ts
|
|
15
|
+
import { createHash } from "crypto";
|
|
16
|
+
import { Hex } from "@aptos-labs/ts-sdk";
|
|
17
|
+
function concatHashes(parts) {
|
|
18
|
+
const combined = Buffer.concat(
|
|
19
|
+
parts.map((part) => Hex.fromHexInput(part).toUint8Array())
|
|
20
|
+
);
|
|
21
|
+
return Hex.fromHexInput(createHash("sha256").update(combined).digest());
|
|
22
|
+
}
|
|
23
|
+
async function generateCommitments(fullData, onChunk, options) {
|
|
24
|
+
const chunksetCommitments = [];
|
|
25
|
+
const chunksetCommitmentHashes = [];
|
|
26
|
+
let rawDataSize = 0;
|
|
27
|
+
const chunksetGen = readInChunks(
|
|
28
|
+
fullData,
|
|
29
|
+
options?.chunksetSize ?? getChunksetSizeBytes()
|
|
30
|
+
);
|
|
31
|
+
for await (let [chunksetIdx, chunksetData] of chunksetGen) {
|
|
32
|
+
const chunkCommitments = [];
|
|
33
|
+
rawDataSize += chunksetData.length;
|
|
34
|
+
chunksetData = zeroPadBuffer(
|
|
35
|
+
chunksetData,
|
|
36
|
+
options?.chunksetSize ?? getChunksetSizeBytes()
|
|
37
|
+
);
|
|
38
|
+
const chunks = await erasureEncode(
|
|
39
|
+
chunksetData,
|
|
40
|
+
options?.erasureK ?? ERASURE_K,
|
|
41
|
+
options?.erasureM ?? ERASURE_M
|
|
42
|
+
);
|
|
43
|
+
let chunkIdx = 0;
|
|
44
|
+
for (const chunkData of chunks) {
|
|
45
|
+
if (onChunk !== void 0) {
|
|
46
|
+
await onChunk(chunksetIdx, chunkIdx, chunkData);
|
|
47
|
+
}
|
|
48
|
+
const chunkHash = concatHashes([chunkData]);
|
|
49
|
+
chunkCommitments.push(chunkHash);
|
|
50
|
+
chunkIdx += 1;
|
|
51
|
+
}
|
|
52
|
+
const h = concatHashes(
|
|
53
|
+
chunkCommitments.map((chunk) => chunk.toUint8Array())
|
|
54
|
+
);
|
|
55
|
+
chunksetCommitments.push({
|
|
56
|
+
chunkset_root: h.toString(),
|
|
57
|
+
chunk_commitments: chunkCommitments.map((chunk) => chunk.toString())
|
|
58
|
+
});
|
|
59
|
+
chunksetCommitmentHashes.push(h);
|
|
60
|
+
}
|
|
61
|
+
return {
|
|
62
|
+
schema_version: "1.3",
|
|
63
|
+
raw_data_size: rawDataSize,
|
|
64
|
+
blob_merkle_root: concatHashes(
|
|
65
|
+
chunksetCommitmentHashes.map((chunk) => chunk.toUint8Array())
|
|
66
|
+
).toString(),
|
|
67
|
+
chunkset_commitments: chunksetCommitments
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export {
|
|
72
|
+
generateCommitments
|
|
73
|
+
};
|
|
File without changes
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ShelbyBlobClient
|
|
3
|
+
} from "./chunk-EFE5Y7IE.mjs";
|
|
4
|
+
import {
|
|
5
|
+
ShelbyRPCClient
|
|
6
|
+
} from "./chunk-DBTBUKCW.mjs";
|
|
7
|
+
import {
|
|
8
|
+
ShelbyClient
|
|
9
|
+
} from "./chunk-YV67F5NY.mjs";
|
|
10
|
+
|
|
11
|
+
// src/node/clients/ShelbyNodeClient.ts
|
|
12
|
+
var ShelbyNodeClient = class extends ShelbyClient {
|
|
13
|
+
/**
|
|
14
|
+
* The coordination client is used to interact with the Aptos blockchain which handles the commitments
|
|
15
|
+
* and metadata for blobs.
|
|
16
|
+
*/
|
|
17
|
+
coordination;
|
|
18
|
+
/**
|
|
19
|
+
* The RPC client is used to interact with the Shelby RPC node which can be responsible for storing,
|
|
20
|
+
* confirming, and retrieving blobs from the storage layer.
|
|
21
|
+
*/
|
|
22
|
+
rpc;
|
|
23
|
+
/**
|
|
24
|
+
* The ShelbyNodeClient is used to interact with the Shelby network.
|
|
25
|
+
*
|
|
26
|
+
* @param config.aptos.config - The Aptos config.
|
|
27
|
+
* @param config.shelby.baseUrl - The base URL of the Shelby RPC node. If not provided, the default base URL will be used.
|
|
28
|
+
* @param config.shelby.deployer - The deployer account address of the Shelby contract. If not provided, the default deployer address will be used.
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```typescript
|
|
32
|
+
* const aptos = new Aptos(new AptosConfig({ network: Network.DEVNET }));
|
|
33
|
+
* const client = new ShelbyNodeClient({ aptos, shelby: { baseUrl: "https://api.shelby.dev" } });
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
constructor(config) {
|
|
37
|
+
super(config);
|
|
38
|
+
this.coordination = new ShelbyBlobClient({
|
|
39
|
+
aptos: this.aptos,
|
|
40
|
+
shelbyDeployer: config.shelby?.deployer
|
|
41
|
+
});
|
|
42
|
+
this.rpc = new ShelbyRPCClient({ aptos: this.aptos });
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Uploads a blob to the Shelby network.
|
|
46
|
+
*
|
|
47
|
+
* @param params.blobData - The data to upload.
|
|
48
|
+
* @param params.signer - The signer of the transaction.
|
|
49
|
+
* @param params.blobName - The name of the blob.
|
|
50
|
+
* @param params.expirationMicros - The expiration time of the blob in microseconds.
|
|
51
|
+
* @param params.options - The options for the upload.
|
|
52
|
+
*
|
|
53
|
+
* @returns The transaction and generated blob commitments.
|
|
54
|
+
*/
|
|
55
|
+
async upload(params) {
|
|
56
|
+
const { transaction, blobCommitments } = await this.coordination.writeBlobCommitments(params);
|
|
57
|
+
const confirmedTransaction = await this.aptos.waitForTransaction({
|
|
58
|
+
transactionHash: transaction.hash
|
|
59
|
+
});
|
|
60
|
+
await this.rpc.putBlob({
|
|
61
|
+
...params,
|
|
62
|
+
account: params.signer.accountAddress
|
|
63
|
+
});
|
|
64
|
+
return { transaction: confirmedTransaction, blobCommitments };
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Downloads a blob from the Shelby RPC node.
|
|
68
|
+
*
|
|
69
|
+
* @param params.account - The account namespace the blob is stored in (e.g. "0x1")
|
|
70
|
+
* @param params.blobName - The name of the blob (e.g. "foo/bar")
|
|
71
|
+
* @param params.range - The range of the blob to download.
|
|
72
|
+
*
|
|
73
|
+
* @returns A `ShelbyBlob` object containing the blob data.
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* ```typescript
|
|
77
|
+
* const blob = await client.download({
|
|
78
|
+
* account,
|
|
79
|
+
* blobName: "foo/bar.txt",
|
|
80
|
+
* });
|
|
81
|
+
* ```
|
|
82
|
+
*/
|
|
83
|
+
async download(params) {
|
|
84
|
+
return await this.rpc.getBlob(params);
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
export {
|
|
89
|
+
ShelbyNodeClient
|
|
90
|
+
};
|