@parity/product-sdk-cloud-storage 0.5.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/LICENSE +201 -0
- package/dist/index.d.ts +600 -0
- package/dist/index.js +534 -0
- package/dist/index.js.map +1 -0
- package/package.json +40 -0
- package/src/authorization.ts +540 -0
- package/src/cid.ts +214 -0
- package/src/client.ts +316 -0
- package/src/errors.ts +162 -0
- package/src/index.ts +107 -0
- package/src/lazy-signer.ts +113 -0
- package/src/networks.ts +47 -0
- package/src/query.ts +216 -0
- package/src/resolve-query.ts +198 -0
- package/src/types.ts +49 -0
- package/src/verify.ts +384 -0
package/src/types.ts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { BulletinTypedApi } from "@parity/bulletin-sdk";
|
|
2
|
+
|
|
3
|
+
/** Typed API for the Cloud Storage (re-export from upstream BulletinTypedApi). */
|
|
4
|
+
export type CloudStorageApi = BulletinTypedApi;
|
|
5
|
+
|
|
6
|
+
/** Re-exported environment string from chain-client. */
|
|
7
|
+
export type { Environment } from "@parity/product-sdk-chain-client";
|
|
8
|
+
|
|
9
|
+
/** //TODO: Come back to this (code docs might need update)
|
|
10
|
+
* Authorization status for a Cloud Storage account.
|
|
11
|
+
*
|
|
12
|
+
* Returned by {@link checkAuthorization} as a pre-flight check before storing
|
|
13
|
+
* data. Consumers can use this to show "not authorized" or "insufficient quota"
|
|
14
|
+
* messages instead of letting the transaction fail mid-execution.
|
|
15
|
+
*/
|
|
16
|
+
export interface AuthorizationStatus {
|
|
17
|
+
/** Whether an authorization entry exists for this account. */
|
|
18
|
+
authorized: boolean;
|
|
19
|
+
/** Remaining transactions allowed. 0 if not authorized. */
|
|
20
|
+
remainingTransactions: number;
|
|
21
|
+
/** Remaining bytes allowed. 0n if not authorized. */
|
|
22
|
+
remainingBytes: bigint;
|
|
23
|
+
/** Block number when the authorization expires. 0 if not authorized. */
|
|
24
|
+
expiration: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Options for {@link CloudStorageClient.fetchBytes} / {@link CloudStorageClient.fetchJson}.
|
|
29
|
+
*/
|
|
30
|
+
export interface QueryOptions {
|
|
31
|
+
/**
|
|
32
|
+
* Timeout for the host preimage lookup subscription, in ms.
|
|
33
|
+
* Default: 30_000. Applied per lookup — for chunked content (DAG-PB
|
|
34
|
+
* manifest CIDs), the manifest fetch and each child chunk fetch
|
|
35
|
+
* each get this budget.
|
|
36
|
+
*/
|
|
37
|
+
lookupTimeoutMs?: number;
|
|
38
|
+
/**
|
|
39
|
+
* When `true`, return the raw bytes for the requested CID without
|
|
40
|
+
* parsing or recursing into a DAG-PB manifest. Default: `false` — the
|
|
41
|
+
* client transparently reassembles chunked content so callers don't
|
|
42
|
+
* need to know whether a CID points at a single chunk or a manifest.
|
|
43
|
+
*
|
|
44
|
+
* Set this if you want to inspect the manifest itself, e.g., to read
|
|
45
|
+
* `unixfs.fileSize()` ahead of fetching, or to drive your own chunk
|
|
46
|
+
* pipeline.
|
|
47
|
+
*/
|
|
48
|
+
noReassemble?: boolean;
|
|
49
|
+
}
|
package/src/verify.ts
ADDED
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chain-storage verification for stored CIDs.
|
|
3
|
+
*
|
|
4
|
+
* The bulletin chain doesn't store content bytes on-chain — `TransactionStorage
|
|
5
|
+
* .Transactions[block]` holds metadata only (`{ chunk_root, content_hash,
|
|
6
|
+
* hashing, cid_codec, size, block_chunks }`), and the bytes themselves live
|
|
7
|
+
* in IPFS. So "read by CID from chain" isn't possible. What *is* possible is
|
|
8
|
+
* proving that a given CID was stored: parse the CID's digest + multihash
|
|
9
|
+
* code, then look it up in `TransactionStorage.Transactions` and confirm a
|
|
10
|
+
* matching `content_hash` + `hashing`.
|
|
11
|
+
*
|
|
12
|
+
* Common use case: just-after-upload UX — `await client.store(data).send()`
|
|
13
|
+
* gives you back `{ cid, blockNumber, extrinsicIndex? }`, and a follow-up
|
|
14
|
+
* `verifyStored(api, cid, { block: blockNumber })` confirms the metadata
|
|
15
|
+
* landed where expected.
|
|
16
|
+
*/
|
|
17
|
+
import { CID } from "multiformats/cid";
|
|
18
|
+
|
|
19
|
+
import { HashAlgorithm } from "./cid.js";
|
|
20
|
+
import { CloudStorageCidError } from "./errors.js";
|
|
21
|
+
import type { CloudStorageApi } from "./types.js";
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Match a multihash code in a CID against the chain's `hashing` enum value.
|
|
25
|
+
*/
|
|
26
|
+
const HASH_CODE_TO_ENUM_TYPE: Record<number, "Blake2b256" | "Sha2_256" | "Keccak256"> = {
|
|
27
|
+
[HashAlgorithm.Blake2b256]: "Blake2b256",
|
|
28
|
+
[HashAlgorithm.Sha2_256]: "Sha2_256",
|
|
29
|
+
[HashAlgorithm.Keccak256]: "Keccak256",
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/** A single matched entry from `TransactionStorage.Transactions`. */
|
|
33
|
+
export interface ChainStoredEntry {
|
|
34
|
+
/** Block number where the transaction was included. */
|
|
35
|
+
block: number;
|
|
36
|
+
/** Index of the entry within the block's transactions array. */
|
|
37
|
+
index: number;
|
|
38
|
+
/** Size of the stored data in bytes (from chain metadata). */
|
|
39
|
+
size: number;
|
|
40
|
+
/** Number of chunks (1 for unchunked data, >1 for chunked + manifest). */
|
|
41
|
+
blockChunks: number;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Verification options for {@link verifyStored}.
|
|
46
|
+
*/
|
|
47
|
+
export interface VerifyStoredOptions {
|
|
48
|
+
/**
|
|
49
|
+
* Block number to look up. Pass the `blockNumber` returned from a prior
|
|
50
|
+
* `store(...).send()` for an O(1) lookup.
|
|
51
|
+
*
|
|
52
|
+
* If omitted, throws — full-chain scans are not supported because
|
|
53
|
+
* `RetentionPeriod` can be many days of blocks. Use `getEntries()` on
|
|
54
|
+
* `api.query.TransactionStorage.Transactions` directly if you need that.
|
|
55
|
+
*/
|
|
56
|
+
block: number;
|
|
57
|
+
/**
|
|
58
|
+
* Optional: index within the block. When provided, narrows verification
|
|
59
|
+
* to that exact slot. Useful when re-checking a known `(block, index)`
|
|
60
|
+
* tuple from an earlier receipt.
|
|
61
|
+
*/
|
|
62
|
+
index?: number;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Verify that a CID is recorded in the cloud storage (bulletin chain's `Transactions` storage)
|
|
67
|
+
* at the given block.
|
|
68
|
+
*
|
|
69
|
+
* Returns the matched entry (with block + index) when the CID's content
|
|
70
|
+
* hash and hashing algorithm both match a `Transactions[block]` entry.
|
|
71
|
+
* Returns `null` when no match is found at that block.
|
|
72
|
+
*
|
|
73
|
+
* @param api - Typed Cloud Storage API instance.
|
|
74
|
+
* @param cid - CIDv1 string to look up.
|
|
75
|
+
* @param options - Verification target (block number, optional index).
|
|
76
|
+
*
|
|
77
|
+
* @example
|
|
78
|
+
* ```ts
|
|
79
|
+
* const receipt = await client.store(data).send();
|
|
80
|
+
* if (receipt.blockNumber !== undefined) {
|
|
81
|
+
* const entry = await verifyStored(client.api, receipt.cid!.toString(), {
|
|
82
|
+
* block: receipt.blockNumber,
|
|
83
|
+
* index: receipt.extrinsicIndex,
|
|
84
|
+
* });
|
|
85
|
+
* if (!entry) console.warn("CID not found in expected block — chain reorg?");
|
|
86
|
+
* }
|
|
87
|
+
* ```
|
|
88
|
+
*/
|
|
89
|
+
export async function verifyStored(
|
|
90
|
+
api: CloudStorageApi,
|
|
91
|
+
cid: string,
|
|
92
|
+
options: VerifyStoredOptions,
|
|
93
|
+
): Promise<ChainStoredEntry | null> {
|
|
94
|
+
const parsed = parseCidForVerify(cid);
|
|
95
|
+
|
|
96
|
+
const queryFn = (api as unknown as TransactionsQueryApi).query?.TransactionStorage?.Transactions
|
|
97
|
+
?.getValue;
|
|
98
|
+
if (!queryFn) {
|
|
99
|
+
throw new Error(
|
|
100
|
+
"CloudStorage API does not expose query.TransactionStorage.Transactions — " +
|
|
101
|
+
"the typed API may be incomplete or the runtime version doesn't match the descriptor.",
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const entries = await queryFn(options.block);
|
|
106
|
+
if (!entries || entries.length === 0) return null;
|
|
107
|
+
|
|
108
|
+
// When an explicit index is provided, check that slot directly — no
|
|
109
|
+
// reason to walk the full array just to skip everything else.
|
|
110
|
+
if (options.index !== undefined) {
|
|
111
|
+
const entry = entries[options.index];
|
|
112
|
+
if (entry && matchesEntry(entry, parsed)) {
|
|
113
|
+
return {
|
|
114
|
+
block: options.block,
|
|
115
|
+
index: options.index,
|
|
116
|
+
size: entry.size,
|
|
117
|
+
blockChunks: entry.block_chunks,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
for (let i = 0; i < entries.length; i++) {
|
|
124
|
+
const entry = entries[i]!;
|
|
125
|
+
if (matchesEntry(entry, parsed)) {
|
|
126
|
+
return {
|
|
127
|
+
block: options.block,
|
|
128
|
+
index: i,
|
|
129
|
+
size: entry.size,
|
|
130
|
+
blockChunks: entry.block_chunks,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
interface ParsedCid {
|
|
139
|
+
digest: Uint8Array;
|
|
140
|
+
hashType: "Blake2b256" | "Sha2_256" | "Keccak256";
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Hand-rolled mirror of `TransactionStorage.Transactions[block][n]` — the
|
|
145
|
+
* shape PAPI returns at runtime when you call `query.TransactionStorage
|
|
146
|
+
* .Transactions.getValue(block)`. Defined here (rather than derived from
|
|
147
|
+
* `BulletinTypedApi`) because the typed API surfaces these values through
|
|
148
|
+
* `Anonymize<I…>` codec aliases that aren't ergonomic to inline.
|
|
149
|
+
*
|
|
150
|
+
* **If the bulletin runtime changes the entry shape, update this here too.**
|
|
151
|
+
* Source of truth: `TransactionInfo` in
|
|
152
|
+
* `packages/descriptors/chains/bulletin/generated/dist/common-types.d.ts`
|
|
153
|
+
* (look for `chunk_root: FixedSizeBinary<32>` to anchor it). When the
|
|
154
|
+
* descriptor regenerates and the fields shift, this interface, the
|
|
155
|
+
* `cid_codec`/`hashing` matching in `matchesEntry`, and the
|
|
156
|
+
* `HASH_CODE_TO_ENUM_TYPE` map above all need to be re-validated together.
|
|
157
|
+
*
|
|
158
|
+
* `Uint8Array | { asBytes(): Uint8Array }` covers both the raw and Binary-
|
|
159
|
+
* wrapped shapes the codec can return depending on configuration.
|
|
160
|
+
*/
|
|
161
|
+
interface ChainEntry {
|
|
162
|
+
chunk_root: { asBytes(): Uint8Array } | Uint8Array;
|
|
163
|
+
content_hash: { asBytes(): Uint8Array } | Uint8Array;
|
|
164
|
+
hashing: { type: "Blake2b256" | "Sha2_256" | "Keccak256" };
|
|
165
|
+
cid_codec: bigint;
|
|
166
|
+
size: number;
|
|
167
|
+
block_chunks: number;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
interface TransactionsQueryApi {
|
|
171
|
+
query?: {
|
|
172
|
+
TransactionStorage?: {
|
|
173
|
+
Transactions?: {
|
|
174
|
+
getValue: (block: number) => Promise<ChainEntry[] | undefined>;
|
|
175
|
+
};
|
|
176
|
+
};
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function parseCidForVerify(cid: string): ParsedCid {
|
|
181
|
+
let parsed;
|
|
182
|
+
try {
|
|
183
|
+
parsed = CID.parse(cid);
|
|
184
|
+
} catch {
|
|
185
|
+
throw new CloudStorageCidError(`Invalid CID: ${cid}`, cid);
|
|
186
|
+
}
|
|
187
|
+
if (parsed.version !== 1) {
|
|
188
|
+
throw new CloudStorageCidError(`Expected CIDv1, got CIDv${parsed.version}`, cid);
|
|
189
|
+
}
|
|
190
|
+
const hashType = HASH_CODE_TO_ENUM_TYPE[parsed.multihash.code];
|
|
191
|
+
if (!hashType) {
|
|
192
|
+
throw new CloudStorageCidError(
|
|
193
|
+
`Unsupported hash algorithm 0x${parsed.multihash.code.toString(16)}`,
|
|
194
|
+
cid,
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
return { digest: parsed.multihash.digest, hashType };
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function matchesEntry(entry: ChainEntry, target: ParsedCid): boolean {
|
|
201
|
+
if (entry.hashing.type !== target.hashType) return false;
|
|
202
|
+
const onChainBytes =
|
|
203
|
+
entry.content_hash instanceof Uint8Array
|
|
204
|
+
? entry.content_hash
|
|
205
|
+
: entry.content_hash.asBytes();
|
|
206
|
+
if (onChainBytes.length !== target.digest.length) return false;
|
|
207
|
+
for (let i = 0; i < onChainBytes.length; i++) {
|
|
208
|
+
if (onChainBytes[i] !== target.digest[i]) return false;
|
|
209
|
+
}
|
|
210
|
+
return true;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (import.meta.vitest) {
|
|
214
|
+
const { describe, test, expect, vi } = import.meta.vitest;
|
|
215
|
+
|
|
216
|
+
function makeMockApi(getValue: (block: number) => Promise<ChainEntry[] | undefined>) {
|
|
217
|
+
return {
|
|
218
|
+
query: {
|
|
219
|
+
TransactionStorage: {
|
|
220
|
+
Transactions: { getValue },
|
|
221
|
+
},
|
|
222
|
+
},
|
|
223
|
+
} as unknown as CloudStorageApi;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function makeEntry(
|
|
227
|
+
digest: Uint8Array,
|
|
228
|
+
hashType: "Blake2b256" | "Sha2_256" | "Keccak256" = "Blake2b256",
|
|
229
|
+
size = 100,
|
|
230
|
+
blockChunks = 1,
|
|
231
|
+
): ChainEntry {
|
|
232
|
+
return {
|
|
233
|
+
chunk_root: digest,
|
|
234
|
+
content_hash: digest,
|
|
235
|
+
hashing: { type: hashType },
|
|
236
|
+
cid_codec: 0x55n,
|
|
237
|
+
size,
|
|
238
|
+
block_chunks: blockChunks,
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Build a real CIDv1 (blake2b-256, raw) we can verify against
|
|
243
|
+
async function makeCidWithDigest(digest: Uint8Array, hashCode = 0xb220): Promise<string> {
|
|
244
|
+
const Digest = await import("multiformats/hashes/digest");
|
|
245
|
+
return CID.createV1(0x55, Digest.create(hashCode, digest)).toString();
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
describe("verifyStored", () => {
|
|
249
|
+
test("returns entry when CID matches at given block", async () => {
|
|
250
|
+
const digest = new Uint8Array(32).fill(0xab);
|
|
251
|
+
const cid = await makeCidWithDigest(digest);
|
|
252
|
+
const api = makeMockApi(vi.fn().mockResolvedValue([makeEntry(digest)]));
|
|
253
|
+
|
|
254
|
+
const result = await verifyStored(api, cid, { block: 100 });
|
|
255
|
+
expect(result).toEqual({ block: 100, index: 0, size: 100, blockChunks: 1 });
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
test("returns null when block has no entries", async () => {
|
|
259
|
+
const digest = new Uint8Array(32).fill(0xab);
|
|
260
|
+
const cid = await makeCidWithDigest(digest);
|
|
261
|
+
const api = makeMockApi(vi.fn().mockResolvedValue(undefined));
|
|
262
|
+
|
|
263
|
+
const result = await verifyStored(api, cid, { block: 100 });
|
|
264
|
+
expect(result).toBeNull();
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
test("returns null when block has entries but none match", async () => {
|
|
268
|
+
const targetDigest = new Uint8Array(32).fill(0xab);
|
|
269
|
+
const otherDigest = new Uint8Array(32).fill(0xcd);
|
|
270
|
+
const cid = await makeCidWithDigest(targetDigest);
|
|
271
|
+
const api = makeMockApi(vi.fn().mockResolvedValue([makeEntry(otherDigest)]));
|
|
272
|
+
|
|
273
|
+
const result = await verifyStored(api, cid, { block: 100 });
|
|
274
|
+
expect(result).toBeNull();
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
test("returns null when hashing algorithm differs", async () => {
|
|
278
|
+
const digest = new Uint8Array(32).fill(0xab);
|
|
279
|
+
// CID uses blake2b-256, chain entry says sha2-256 with same digest bytes
|
|
280
|
+
const cid = await makeCidWithDigest(digest, 0xb220);
|
|
281
|
+
const api = makeMockApi(vi.fn().mockResolvedValue([makeEntry(digest, "Sha2_256")]));
|
|
282
|
+
|
|
283
|
+
const result = await verifyStored(api, cid, { block: 100 });
|
|
284
|
+
expect(result).toBeNull();
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
test("finds match at correct index when multiple entries exist", async () => {
|
|
288
|
+
const targetDigest = new Uint8Array(32).fill(0xab);
|
|
289
|
+
const filler = new Uint8Array(32).fill(0xcd);
|
|
290
|
+
const cid = await makeCidWithDigest(targetDigest);
|
|
291
|
+
const api = makeMockApi(
|
|
292
|
+
vi
|
|
293
|
+
.fn()
|
|
294
|
+
.mockResolvedValue([
|
|
295
|
+
makeEntry(filler),
|
|
296
|
+
makeEntry(filler),
|
|
297
|
+
makeEntry(targetDigest),
|
|
298
|
+
]),
|
|
299
|
+
);
|
|
300
|
+
|
|
301
|
+
const result = await verifyStored(api, cid, { block: 100 });
|
|
302
|
+
expect(result?.index).toBe(2);
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
test("respects explicit index option", async () => {
|
|
306
|
+
const targetDigest = new Uint8Array(32).fill(0xab);
|
|
307
|
+
const filler = new Uint8Array(32).fill(0xcd);
|
|
308
|
+
const cid = await makeCidWithDigest(targetDigest);
|
|
309
|
+
// Target is at index 2, but caller says index 0 — should not match
|
|
310
|
+
const api = makeMockApi(
|
|
311
|
+
vi
|
|
312
|
+
.fn()
|
|
313
|
+
.mockResolvedValue([
|
|
314
|
+
makeEntry(filler),
|
|
315
|
+
makeEntry(filler),
|
|
316
|
+
makeEntry(targetDigest),
|
|
317
|
+
]),
|
|
318
|
+
);
|
|
319
|
+
|
|
320
|
+
const result = await verifyStored(api, cid, { block: 100, index: 0 });
|
|
321
|
+
expect(result).toBeNull();
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
test("returns the entry when explicit index matches", async () => {
|
|
325
|
+
const targetDigest = new Uint8Array(32).fill(0xab);
|
|
326
|
+
const filler = new Uint8Array(32).fill(0xcd);
|
|
327
|
+
const cid = await makeCidWithDigest(targetDigest);
|
|
328
|
+
const api = makeMockApi(
|
|
329
|
+
vi
|
|
330
|
+
.fn()
|
|
331
|
+
.mockResolvedValue([
|
|
332
|
+
makeEntry(filler),
|
|
333
|
+
makeEntry(filler),
|
|
334
|
+
makeEntry(targetDigest),
|
|
335
|
+
]),
|
|
336
|
+
);
|
|
337
|
+
|
|
338
|
+
const result = await verifyStored(api, cid, { block: 100, index: 2 });
|
|
339
|
+
expect(result?.index).toBe(2);
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
test("throws CloudStorageCidError on invalid CID", async () => {
|
|
343
|
+
const api = makeMockApi(vi.fn());
|
|
344
|
+
await expect(verifyStored(api, "not-a-cid", { block: 1 })).rejects.toThrow(
|
|
345
|
+
CloudStorageCidError,
|
|
346
|
+
);
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
test("throws when api lacks the expected query path", async () => {
|
|
350
|
+
const api = {} as CloudStorageApi;
|
|
351
|
+
const digest = new Uint8Array(32).fill(0xab);
|
|
352
|
+
const cid = await makeCidWithDigest(digest);
|
|
353
|
+
await expect(verifyStored(api, cid, { block: 1 })).rejects.toThrow(
|
|
354
|
+
/does not expose query/,
|
|
355
|
+
);
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
test("handles content_hash as a Binary-like wrapper", async () => {
|
|
359
|
+
const digest = new Uint8Array(32).fill(0xab);
|
|
360
|
+
const cid = await makeCidWithDigest(digest);
|
|
361
|
+
const wrapper = { asBytes: () => digest };
|
|
362
|
+
const entry: ChainEntry = {
|
|
363
|
+
chunk_root: wrapper,
|
|
364
|
+
content_hash: wrapper,
|
|
365
|
+
hashing: { type: "Blake2b256" },
|
|
366
|
+
cid_codec: 0x55n,
|
|
367
|
+
size: 50,
|
|
368
|
+
block_chunks: 1,
|
|
369
|
+
};
|
|
370
|
+
const api = makeMockApi(vi.fn().mockResolvedValue([entry]));
|
|
371
|
+
const result = await verifyStored(api, cid, { block: 1 });
|
|
372
|
+
expect(result).toEqual({ block: 1, index: 0, size: 50, blockChunks: 1 });
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
test("passes the block number to the storage call", async () => {
|
|
376
|
+
const digest = new Uint8Array(32).fill(0xab);
|
|
377
|
+
const cid = await makeCidWithDigest(digest);
|
|
378
|
+
const getValue = vi.fn().mockResolvedValue([makeEntry(digest)]);
|
|
379
|
+
const api = makeMockApi(getValue);
|
|
380
|
+
await verifyStored(api, cid, { block: 42 });
|
|
381
|
+
expect(getValue).toHaveBeenCalledWith(42);
|
|
382
|
+
});
|
|
383
|
+
});
|
|
384
|
+
}
|