@dedot/api 0.15.2 → 0.15.3-next.65898ecf.10
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/chaintypes/substrate/index.d.ts +21 -1
- package/chaintypes/substrate/index.js +0 -1
- package/cjs/chaintypes/substrate/index.js +0 -1
- package/cjs/client/DedotClient.js +39 -6
- package/cjs/executor/Executor.js +1 -0
- package/cjs/executor/v2/StorageQueryExecutorV2.js +1 -3
- package/cjs/executor/v2/TxExecutorV2.js +5 -3
- package/cjs/json-rpc/group/Archive.js +226 -0
- package/cjs/json-rpc/group/ChainHead/ChainHead.js +121 -44
- package/cjs/json-rpc/group/ChainHead/error.js +5 -0
- package/cjs/json-rpc/group/index.js +1 -0
- package/cjs/json-rpc/subscriptionsInfo.js +4 -0
- package/cjs/storage/LegacyStorageQuery.js +5 -0
- package/cjs/storage/NewStorageQuery.js +5 -0
- package/client/BaseSubstrateClient.d.ts +3 -3
- package/client/DedotClient.d.ts +6 -4
- package/client/DedotClient.js +42 -9
- package/client/LegacyClient.d.ts +2 -2
- package/executor/Executor.d.ts +5 -5
- package/executor/Executor.js +1 -0
- package/executor/StorageQueryExecutor.d.ts +2 -2
- package/executor/v2/RuntimeApiExecutorV2.d.ts +2 -2
- package/executor/v2/StorageQueryExecutorV2.d.ts +4 -4
- package/executor/v2/StorageQueryExecutorV2.js +1 -3
- package/executor/v2/TxExecutorV2.d.ts +2 -1
- package/executor/v2/TxExecutorV2.js +5 -3
- package/executor/v2/ViewFunctionExecutorV2.d.ts +2 -2
- package/extrinsic/extensions/SignedExtension.d.ts +3 -3
- package/extrinsic/submittable/BaseSubmittableExtrinsic.d.ts +2 -2
- package/extrinsic/submittable/SubmittableExtrinsicV2.d.ts +2 -2
- package/json-rpc/group/Archive.d.ts +134 -0
- package/json-rpc/group/Archive.js +222 -0
- package/json-rpc/group/ChainHead/ChainHead.d.ts +10 -0
- package/json-rpc/group/ChainHead/ChainHead.js +121 -44
- package/json-rpc/group/ChainHead/error.d.ts +3 -0
- package/json-rpc/group/ChainHead/error.js +5 -0
- package/json-rpc/group/index.d.ts +1 -0
- package/json-rpc/group/index.js +1 -0
- package/json-rpc/subscriptionsInfo.js +4 -0
- package/package.json +9 -9
- package/storage/BaseStorageQuery.d.ts +5 -6
- package/storage/LegacyStorageQuery.d.ts +4 -3
- package/storage/LegacyStorageQuery.js +5 -0
- package/storage/NewStorageQuery.d.ts +5 -4
- package/storage/NewStorageQuery.js +5 -0
- package/storage/QueryableStorage.d.ts +2 -2
- package/types.d.ts +10 -5
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import { DedotError } from '@dedot/utils';
|
|
2
|
+
import { JsonRpcGroup } from './JsonRpcGroup.js';
|
|
3
|
+
/**
|
|
4
|
+
* @name Archive
|
|
5
|
+
* Archive JSON-RPC methods for accessing historical blockchain data.
|
|
6
|
+
* Functions with the `archive` prefix allow obtaining the state of the chain
|
|
7
|
+
* at any point in the present or in the past.
|
|
8
|
+
*
|
|
9
|
+
* JSON-RPC V2: https://paritytech.github.io/json-rpc-interface-spec/api/archive.html
|
|
10
|
+
*/
|
|
11
|
+
export class Archive extends JsonRpcGroup {
|
|
12
|
+
#genesisHash;
|
|
13
|
+
#cache;
|
|
14
|
+
constructor(client, options) {
|
|
15
|
+
super(client, { prefix: 'archive', supportedVersions: ['unstable', 'v1'], ...options });
|
|
16
|
+
this.#cache = new Map();
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Retrieves the body (list of transactions) of a given block.
|
|
20
|
+
* Returns an array of strings containing the hexadecimal-encoded SCALE-codec-encoded
|
|
21
|
+
* transactions in that block. If no block with that hash is found, null.
|
|
22
|
+
*
|
|
23
|
+
* @param hash - The block hash (optional, defaults to current finalized block)
|
|
24
|
+
* @returns Array of transaction hashes or null if block not found
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```typescript
|
|
28
|
+
* // Get transactions from current finalized block
|
|
29
|
+
* const transactions = await archive.body();
|
|
30
|
+
*
|
|
31
|
+
* // Get transactions from specific block
|
|
32
|
+
* const transactions = await archive.body('0x1234...');
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
async body(hash) {
|
|
36
|
+
const blockHash = hash || (await this.finalizedHash());
|
|
37
|
+
const cacheKey = `${blockHash}::body`;
|
|
38
|
+
if (this.#cache.has(cacheKey)) {
|
|
39
|
+
return this.#cache.get(cacheKey);
|
|
40
|
+
}
|
|
41
|
+
const result = await this.send('body', blockHash);
|
|
42
|
+
this.#cache.set(cacheKey, result);
|
|
43
|
+
return result;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Get the chain's genesis hash.
|
|
47
|
+
* Returns a string containing the hexadecimal-encoded hash of the genesis block of the chain.
|
|
48
|
+
* This value is cached after the first call.
|
|
49
|
+
*
|
|
50
|
+
* @returns The genesis block hash
|
|
51
|
+
*/
|
|
52
|
+
async genesisHash() {
|
|
53
|
+
if (!this.#genesisHash) {
|
|
54
|
+
this.#genesisHash = await this.send('genesisHash');
|
|
55
|
+
}
|
|
56
|
+
return this.#genesisHash;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Get the block's header.
|
|
60
|
+
* Returns a string containing the hexadecimal-encoded SCALE-codec encoding header of the block.
|
|
61
|
+
*
|
|
62
|
+
* @param hash - The block hash (optional, defaults to current finalized block)
|
|
63
|
+
* @returns The encoded block header or null if block not found
|
|
64
|
+
*
|
|
65
|
+
* @example
|
|
66
|
+
* ```typescript
|
|
67
|
+
* // Get header of current finalized block
|
|
68
|
+
* const header = await archive.header();
|
|
69
|
+
*
|
|
70
|
+
* // Get header of specific block
|
|
71
|
+
* const header = await archive.header('0x1234...');
|
|
72
|
+
* ```
|
|
73
|
+
*/
|
|
74
|
+
async header(hash) {
|
|
75
|
+
const blockHash = hash || (await this.finalizedHash());
|
|
76
|
+
const cacheKey = `${blockHash}::header`;
|
|
77
|
+
if (this.#cache.has(cacheKey)) {
|
|
78
|
+
return this.#cache.get(cacheKey);
|
|
79
|
+
}
|
|
80
|
+
const result = await this.send('header', blockHash);
|
|
81
|
+
this.#cache.set(cacheKey, result);
|
|
82
|
+
return result;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Get the height of the current finalized block.
|
|
86
|
+
* Returns an integer height of the current finalized block of the chain.
|
|
87
|
+
*
|
|
88
|
+
* @returns The height of the finalized block
|
|
89
|
+
*/
|
|
90
|
+
async finalizedHeight() {
|
|
91
|
+
return this.send('finalizedHeight');
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Get the hash of the current finalized block.
|
|
95
|
+
* Returns a string containing the hexadecimal-encoded hash of the current finalized block.
|
|
96
|
+
* This is a convenience method that combines finalizedHeight() and hashByHeight().
|
|
97
|
+
*
|
|
98
|
+
* @returns The hash of the current finalized block
|
|
99
|
+
*/
|
|
100
|
+
async finalizedHash() {
|
|
101
|
+
const height = await this.finalizedHeight();
|
|
102
|
+
const hashes = await this.hashByHeight(height);
|
|
103
|
+
if (hashes.length === 0) {
|
|
104
|
+
throw new Error(`No block found at finalized height ${height}`);
|
|
105
|
+
}
|
|
106
|
+
return hashes[0];
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Get the hashes of blocks from the given height.
|
|
110
|
+
* Returns an array (possibly empty) of strings containing hexadecimal-encoded hashes of block headers.
|
|
111
|
+
*
|
|
112
|
+
* Note: For heights <= finalized height, there is guaranteed to be one block.
|
|
113
|
+
* For heights > finalized height, there may be zero, one or multiple blocks depending on forks.
|
|
114
|
+
*
|
|
115
|
+
* @param height - The block height
|
|
116
|
+
* @returns Array of block hashes at the given height
|
|
117
|
+
*/
|
|
118
|
+
async hashByHeight(height) {
|
|
119
|
+
return this.send('hashByHeight', height);
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Call into the Runtime API at a specified block's state.
|
|
123
|
+
*
|
|
124
|
+
* @param func - The runtime API function to call
|
|
125
|
+
* @param params - The parameters for the function call (SCALE-encoded)
|
|
126
|
+
* @param hash - The block hash (optional, defaults to current finalized block)
|
|
127
|
+
* @returns The result of the runtime call
|
|
128
|
+
*
|
|
129
|
+
* @example
|
|
130
|
+
* ```typescript
|
|
131
|
+
* // Call Core_version on current finalized block
|
|
132
|
+
* const version = await archive.call('Core_version', '0x');
|
|
133
|
+
*
|
|
134
|
+
* // Call Core_version on specific block
|
|
135
|
+
* const version = await archive.call('Core_version', '0x', '0x1234...');
|
|
136
|
+
* ```
|
|
137
|
+
*/
|
|
138
|
+
async call(func, params, hash) {
|
|
139
|
+
const blockHash = hash || (await this.finalizedHash());
|
|
140
|
+
const cacheKey = `${blockHash}::call::${func}::${params}`;
|
|
141
|
+
if (this.#cache.has(cacheKey)) {
|
|
142
|
+
return this.#cache.get(cacheKey);
|
|
143
|
+
}
|
|
144
|
+
const result = await this.send('call', blockHash, func, params);
|
|
145
|
+
if (!result.success) {
|
|
146
|
+
throw new DedotError(result.error);
|
|
147
|
+
}
|
|
148
|
+
this.#cache.set(cacheKey, result.value);
|
|
149
|
+
return result.value;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Returns storage entries at a specific block's state via subscription.
|
|
153
|
+
*
|
|
154
|
+
* @param items - Array of storage queries with optional pagination
|
|
155
|
+
* @param childTrie - Optional child trie key
|
|
156
|
+
* @param callback - Callback to receive storage events
|
|
157
|
+
* @param hash - The block hash (optional, defaults to current finalized block)
|
|
158
|
+
* @returns Unsubscribe function
|
|
159
|
+
*/
|
|
160
|
+
async #storageSubscription(items, childTrie, callback, hash) {
|
|
161
|
+
const blockHash = hash || (await this.finalizedHash());
|
|
162
|
+
return this.send('storage', blockHash, items, childTrie, callback);
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Returns storage entries at a specific block's state.
|
|
166
|
+
* This method collects all storage events and returns them as a single result.
|
|
167
|
+
*
|
|
168
|
+
* @param items - Array of storage queries with optional pagination
|
|
169
|
+
* @param childTrie - Optional child trie key
|
|
170
|
+
* @param hash - The block hash (optional, defaults to current finalized block)
|
|
171
|
+
* @returns Storage results array
|
|
172
|
+
*
|
|
173
|
+
* @example
|
|
174
|
+
* ```typescript
|
|
175
|
+
* // Query storage from current finalized block
|
|
176
|
+
* const results = await archive.storage([{ key: '0x1234', type: 'value' }]);
|
|
177
|
+
*
|
|
178
|
+
* // Query storage from specific block
|
|
179
|
+
* const results = await archive.storage([{ key: '0x1234', type: 'value' }], null, '0xabcd...');
|
|
180
|
+
* ```
|
|
181
|
+
*/
|
|
182
|
+
async storage(items, childTrie, hash) {
|
|
183
|
+
return new Promise(async (resolve, reject) => {
|
|
184
|
+
const results = [];
|
|
185
|
+
const blockHash = hash || (await this.finalizedHash());
|
|
186
|
+
// Generate cache key
|
|
187
|
+
const cacheKey = `${blockHash}::storage::${JSON.stringify(items)}::${childTrie ?? null}`;
|
|
188
|
+
// Check cache
|
|
189
|
+
if (this.#cache.has(cacheKey)) {
|
|
190
|
+
return resolve(this.#cache.get(cacheKey));
|
|
191
|
+
}
|
|
192
|
+
this.#storageSubscription(items, childTrie || null, (event) => {
|
|
193
|
+
switch (event.event) {
|
|
194
|
+
case 'storage':
|
|
195
|
+
results.push(event);
|
|
196
|
+
break;
|
|
197
|
+
case 'storageDone':
|
|
198
|
+
// Set cache after successful completion
|
|
199
|
+
this.#cache.set(cacheKey, results);
|
|
200
|
+
resolve(results);
|
|
201
|
+
break;
|
|
202
|
+
case 'storageError':
|
|
203
|
+
reject(new DedotError(event.error));
|
|
204
|
+
break;
|
|
205
|
+
}
|
|
206
|
+
}, blockHash).catch(reject);
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Clears the internal cache used for storing archive query results.
|
|
211
|
+
* This can be useful for memory management or when you want to force fresh data retrieval.
|
|
212
|
+
*
|
|
213
|
+
* @example
|
|
214
|
+
* ```typescript
|
|
215
|
+
* // Clear all cached results
|
|
216
|
+
* archive.clearCache();
|
|
217
|
+
* ```
|
|
218
|
+
*/
|
|
219
|
+
clearCache() {
|
|
220
|
+
this.#cache.clear();
|
|
221
|
+
}
|
|
222
|
+
}
|
|
@@ -2,6 +2,7 @@ import { BlockHash, Option } from '@dedot/codecs';
|
|
|
2
2
|
import type { ChainHeadRuntimeVersion, OperationId, StorageQuery, StorageResult } from '@dedot/types/json-rpc';
|
|
3
3
|
import { Deferred, HexString } from '@dedot/utils';
|
|
4
4
|
import type { IJsonRpcClient } from '../../../types.js';
|
|
5
|
+
import { Archive } from '../Archive.js';
|
|
5
6
|
import { JsonRpcGroup, type JsonRpcGroupOptions } from '../JsonRpcGroup.js';
|
|
6
7
|
export type OperationHandler<T = any> = {
|
|
7
8
|
operationId: OperationId;
|
|
@@ -20,6 +21,15 @@ export declare const MIN_FINALIZED_QUEUE_SIZE = 10;
|
|
|
20
21
|
export declare class ChainHead extends JsonRpcGroup<ChainHeadEvent> {
|
|
21
22
|
#private;
|
|
22
23
|
constructor(client: IJsonRpcClient, options?: Partial<JsonRpcGroupOptions>);
|
|
24
|
+
/**
|
|
25
|
+
* Attach an Archive instance as fallback for operations that fail due to unpinned blocks.
|
|
26
|
+
* When a ChainHeadBlockNotPinnedError occurs, the operation will automatically fallback
|
|
27
|
+
* to the Archive API to attempt to retrieve the data from historical blocks.
|
|
28
|
+
*
|
|
29
|
+
* @param archive - Archive instance to use as fallback
|
|
30
|
+
* @returns this ChainHead instance for method chaining
|
|
31
|
+
*/
|
|
32
|
+
withArchive(archive: Archive): this;
|
|
23
33
|
runtimeVersion(): Promise<ChainHeadRuntimeVersion>;
|
|
24
34
|
bestRuntimeVersion(): Promise<ChainHeadRuntimeVersion>;
|
|
25
35
|
finalizedHash(): Promise<BlockHash>;
|
|
@@ -20,6 +20,17 @@ export class ChainHead extends JsonRpcGroup {
|
|
|
20
20
|
#blockUsage;
|
|
21
21
|
#cache;
|
|
22
22
|
#operationQueue;
|
|
23
|
+
/**
|
|
24
|
+
* Archive instance used as fallback when ChainHead blocks are not pinned.
|
|
25
|
+
*
|
|
26
|
+
* When ChainHead operations fail with ChainHeadBlockNotPinnedError, the system
|
|
27
|
+
* automatically attempts the same operation using the Archive API. This provides
|
|
28
|
+
* seamless access to historical blockchain data even when blocks are no longer
|
|
29
|
+
* maintained in the ChainHead's pinned block set.
|
|
30
|
+
*
|
|
31
|
+
* @private
|
|
32
|
+
*/
|
|
33
|
+
#archive;
|
|
23
34
|
constructor(client, options) {
|
|
24
35
|
super(client, { prefix: 'chainHead', supportedVersions: ['unstable', 'v1'], ...options });
|
|
25
36
|
this.#handlers = {};
|
|
@@ -33,6 +44,18 @@ export class ChainHead extends JsonRpcGroup {
|
|
|
33
44
|
// This helps us to not accidentally putting too much stress on the JSON-RPC server, especially smoldot/light-client
|
|
34
45
|
this.#operationQueue = new ThrottleQueue(this.#__unsafe__isSmoldot() ? 25 : 250);
|
|
35
46
|
}
|
|
47
|
+
/**
|
|
48
|
+
* Attach an Archive instance as fallback for operations that fail due to unpinned blocks.
|
|
49
|
+
* When a ChainHeadBlockNotPinnedError occurs, the operation will automatically fallback
|
|
50
|
+
* to the Archive API to attempt to retrieve the data from historical blocks.
|
|
51
|
+
*
|
|
52
|
+
* @param archive - Archive instance to use as fallback
|
|
53
|
+
* @returns this ChainHead instance for method chaining
|
|
54
|
+
*/
|
|
55
|
+
withArchive(archive) {
|
|
56
|
+
this.#archive = archive;
|
|
57
|
+
return this;
|
|
58
|
+
}
|
|
36
59
|
async runtimeVersion() {
|
|
37
60
|
await this.#ensureFollowed();
|
|
38
61
|
return this.#finalizedRuntime;
|
|
@@ -341,11 +364,39 @@ export class ChainHead extends JsonRpcGroup {
|
|
|
341
364
|
return hash;
|
|
342
365
|
}
|
|
343
366
|
else {
|
|
344
|
-
throw new ChainHeadBlockNotPinnedError(`Block hash ${hash} is not pinned
|
|
367
|
+
throw new ChainHeadBlockNotPinnedError(`Block hash ${hash} is not pinned`, hash);
|
|
345
368
|
}
|
|
346
369
|
}
|
|
347
370
|
return ensurePresence(this.#bestHash || this.#finalizedHash);
|
|
348
371
|
}
|
|
372
|
+
/**
|
|
373
|
+
* Executes a ChainHead operation with automatic Archive fallback.
|
|
374
|
+
*
|
|
375
|
+
* This method first attempts the primary ChainHead operation. If it fails with
|
|
376
|
+
* ChainHeadBlockNotPinnedError (indicating the block is no longer pinned), and
|
|
377
|
+
* an Archive instance is available, it automatically retries the operation using
|
|
378
|
+
* the Archive API.
|
|
379
|
+
*
|
|
380
|
+
* @param operation - Primary ChainHead operation to attempt
|
|
381
|
+
* @param fallback - Archive operation to fallback to
|
|
382
|
+
* @param hash - Block hash being accessed (for logging)
|
|
383
|
+
* @returns Result from either ChainHead or Archive operation
|
|
384
|
+
* @throws Original error if not a pinning error or no Archive available
|
|
385
|
+
* @private
|
|
386
|
+
*/
|
|
387
|
+
async #tryWithArchive(operation, fallback) {
|
|
388
|
+
try {
|
|
389
|
+
return await operation();
|
|
390
|
+
}
|
|
391
|
+
catch (error) {
|
|
392
|
+
if (error instanceof ChainHeadBlockNotPinnedError && this.#archive) {
|
|
393
|
+
const errorHash = error.hash;
|
|
394
|
+
console.warn(`Block ${errorHash} not pinned in ChainHead, falling back to Archive`);
|
|
395
|
+
return await fallback(this.#archive, errorHash);
|
|
396
|
+
}
|
|
397
|
+
throw error;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
349
400
|
#getOperationHandler(result) {
|
|
350
401
|
const handler = this.#handlers[result.operationId];
|
|
351
402
|
if (handler)
|
|
@@ -475,21 +526,31 @@ export class ChainHead extends JsonRpcGroup {
|
|
|
475
526
|
async body(at) {
|
|
476
527
|
await this.#ensureFollowed();
|
|
477
528
|
const shouldRetryOnPrunedBlock = !at;
|
|
478
|
-
|
|
529
|
+
const operation = async () => {
|
|
479
530
|
const atHash = this.#ensurePinnedHash(at);
|
|
480
531
|
const cacheKey = `${atHash}::body`;
|
|
481
532
|
if (this.#cache.has(cacheKey)) {
|
|
482
533
|
return this.#cache.get(cacheKey);
|
|
483
534
|
}
|
|
484
|
-
const
|
|
535
|
+
const bodyOperation = async () => {
|
|
485
536
|
await this.#ensureFollowed();
|
|
486
537
|
const hash = this.#ensurePinnedHash(atHash);
|
|
487
538
|
const resp = await this.send('body', this.#subscriptionId, hash);
|
|
488
539
|
return this.#awaitOperation(resp, hash);
|
|
489
540
|
};
|
|
490
|
-
const resp = await this.#operationQueue.add(() => this.#performOperationWithRetry(
|
|
541
|
+
const resp = await this.#operationQueue.add(() => this.#performOperationWithRetry(bodyOperation, atHash));
|
|
491
542
|
this.#cache.set(cacheKey, resp);
|
|
492
543
|
return resp;
|
|
544
|
+
};
|
|
545
|
+
const fallback = async (archive, hash) => {
|
|
546
|
+
const result = await archive.body(hash);
|
|
547
|
+
if (result === undefined) {
|
|
548
|
+
throw new ChainHeadOperationError(`Block ${hash} not found in Archive`);
|
|
549
|
+
}
|
|
550
|
+
return result;
|
|
551
|
+
};
|
|
552
|
+
try {
|
|
553
|
+
return await this.#tryWithArchive(operation, fallback);
|
|
493
554
|
}
|
|
494
555
|
catch (e) {
|
|
495
556
|
if (e instanceof ChainHeadBlockPrunedError && shouldRetryOnPrunedBlock) {
|
|
@@ -504,21 +565,25 @@ export class ChainHead extends JsonRpcGroup {
|
|
|
504
565
|
async call(func, params = '0x', at) {
|
|
505
566
|
await this.#ensureFollowed();
|
|
506
567
|
const shouldRetryOnPrunedBlock = !at;
|
|
507
|
-
|
|
568
|
+
const operation = async () => {
|
|
508
569
|
const atHash = this.#ensurePinnedHash(at);
|
|
509
570
|
const cacheKey = `${atHash}::call::${func}::${params}`;
|
|
510
571
|
if (this.#cache.has(cacheKey)) {
|
|
511
572
|
return this.#cache.get(cacheKey);
|
|
512
573
|
}
|
|
513
|
-
const
|
|
574
|
+
const callOperation = async () => {
|
|
514
575
|
await this.#ensureFollowed();
|
|
515
576
|
const hash = this.#ensurePinnedHash(atHash);
|
|
516
577
|
const resp = await this.send('call', this.#subscriptionId, hash, func, params);
|
|
517
578
|
return this.#awaitOperation(resp, hash);
|
|
518
579
|
};
|
|
519
|
-
const resp = await this.#operationQueue.add(() => this.#performOperationWithRetry(
|
|
580
|
+
const resp = await this.#operationQueue.add(() => this.#performOperationWithRetry(callOperation, atHash));
|
|
520
581
|
this.#cache.set(cacheKey, resp);
|
|
521
582
|
return resp;
|
|
583
|
+
};
|
|
584
|
+
const fallback = (archive, hash) => archive.call(func, params, hash);
|
|
585
|
+
try {
|
|
586
|
+
return await this.#tryWithArchive(operation, fallback);
|
|
522
587
|
}
|
|
523
588
|
catch (e) {
|
|
524
589
|
if (e instanceof ChainHeadBlockPrunedError && shouldRetryOnPrunedBlock) {
|
|
@@ -532,14 +597,18 @@ export class ChainHead extends JsonRpcGroup {
|
|
|
532
597
|
*/
|
|
533
598
|
async header(at) {
|
|
534
599
|
await this.#ensureFollowed();
|
|
535
|
-
const
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
600
|
+
const operation = async () => {
|
|
601
|
+
const hash = this.#ensurePinnedHash(at);
|
|
602
|
+
const cacheKey = `${hash}::header`;
|
|
603
|
+
if (this.#cache.has(cacheKey)) {
|
|
604
|
+
return this.#cache.get(cacheKey);
|
|
605
|
+
}
|
|
606
|
+
const resp = await this.#getHeader(hash);
|
|
607
|
+
this.#cache.set(cacheKey, resp);
|
|
608
|
+
return resp;
|
|
609
|
+
};
|
|
610
|
+
const fallback = (archive, errorHash) => archive.header(errorHash);
|
|
611
|
+
return await this.#tryWithArchive(operation, fallback);
|
|
543
612
|
}
|
|
544
613
|
async #getHeader(at) {
|
|
545
614
|
return await this.send('header', this.#subscriptionId, at);
|
|
@@ -550,35 +619,46 @@ export class ChainHead extends JsonRpcGroup {
|
|
|
550
619
|
async storage(items, childTrie, at) {
|
|
551
620
|
await this.#ensureFollowed();
|
|
552
621
|
const shouldRetryOnPrunedBlock = !at;
|
|
553
|
-
const
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
const
|
|
565
|
-
|
|
566
|
-
|
|
622
|
+
const operation = async () => {
|
|
623
|
+
const hash = this.#ensurePinnedHash(at);
|
|
624
|
+
try {
|
|
625
|
+
// JSON.stringify(items) might get big, we probably should do a twox hashing in such case
|
|
626
|
+
const cacheKey = `${hash}::storage::${JSON.stringify(items)}::${childTrie ?? null}`;
|
|
627
|
+
if (this.#cache.has(cacheKey)) {
|
|
628
|
+
return this.#cache.get(cacheKey);
|
|
629
|
+
}
|
|
630
|
+
this.#blockUsage.use(hash);
|
|
631
|
+
let results = [];
|
|
632
|
+
if (this.#__unsafe__isSmoldot()) {
|
|
633
|
+
const fetchItem = async (item) => {
|
|
634
|
+
const [batch, newDiscardedItems] = await this.#getStorage([item], childTrie ?? null, hash);
|
|
635
|
+
if (newDiscardedItems.length > 0) {
|
|
636
|
+
return fetchItem(item);
|
|
637
|
+
}
|
|
638
|
+
return batch;
|
|
639
|
+
};
|
|
640
|
+
results = (await Promise.all(items.map((one) => fetchItem(one)))).flat();
|
|
641
|
+
}
|
|
642
|
+
else {
|
|
643
|
+
let queryItems = items;
|
|
644
|
+
while (queryItems.length > 0) {
|
|
645
|
+
const [newBatch, newDiscardedItems] = await this.#getStorage(queryItems, childTrie ?? null, hash);
|
|
646
|
+
results.push(...newBatch);
|
|
647
|
+
queryItems = newDiscardedItems;
|
|
567
648
|
}
|
|
568
|
-
return batch;
|
|
569
|
-
};
|
|
570
|
-
results = (await Promise.all(items.map((one) => fetchItem(one)))).flat();
|
|
571
|
-
}
|
|
572
|
-
else {
|
|
573
|
-
let queryItems = items;
|
|
574
|
-
while (queryItems.length > 0) {
|
|
575
|
-
const [newBatch, newDiscardedItems] = await this.#getStorage(queryItems, childTrie ?? null, hash);
|
|
576
|
-
results.push(...newBatch);
|
|
577
|
-
queryItems = newDiscardedItems;
|
|
578
649
|
}
|
|
650
|
+
this.#cache.set(cacheKey, results);
|
|
651
|
+
return results;
|
|
652
|
+
}
|
|
653
|
+
finally {
|
|
654
|
+
this.#blockUsage.release(hash);
|
|
579
655
|
}
|
|
580
|
-
|
|
581
|
-
|
|
656
|
+
};
|
|
657
|
+
const fallback = (archive, hash) => {
|
|
658
|
+
return archive.storage(items, childTrie, hash);
|
|
659
|
+
};
|
|
660
|
+
try {
|
|
661
|
+
return await this.#tryWithArchive(operation, fallback);
|
|
582
662
|
}
|
|
583
663
|
catch (e) {
|
|
584
664
|
if (e instanceof ChainHeadBlockPrunedError && shouldRetryOnPrunedBlock) {
|
|
@@ -586,9 +666,6 @@ export class ChainHead extends JsonRpcGroup {
|
|
|
586
666
|
}
|
|
587
667
|
throw e;
|
|
588
668
|
}
|
|
589
|
-
finally {
|
|
590
|
-
this.#blockUsage.release(hash);
|
|
591
|
-
}
|
|
592
669
|
}
|
|
593
670
|
async #getStorage(items, childTrie, at) {
|
|
594
671
|
const operation = () => this.#getStorageOperation(items, childTrie, this.#ensurePinnedHash(at));
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { BlockHash } from '@dedot/codecs';
|
|
1
2
|
import { DedotError } from '@dedot/utils';
|
|
2
3
|
export declare enum RetryStrategy {
|
|
3
4
|
NOW = "NOW",// Retry immediately
|
|
@@ -49,6 +50,8 @@ export declare class ChainHeadInvalidRuntimeError extends ChainHeadError {
|
|
|
49
50
|
}
|
|
50
51
|
export declare class ChainHeadBlockNotPinnedError extends ChainHeadError {
|
|
51
52
|
name: string;
|
|
53
|
+
hash: BlockHash;
|
|
54
|
+
constructor(message: string, hash: BlockHash | string);
|
|
52
55
|
}
|
|
53
56
|
export declare class ChainHeadBlockPrunedError extends ChainHeadError {
|
|
54
57
|
name: string;
|
|
@@ -50,6 +50,11 @@ export class ChainHeadInvalidRuntimeError extends ChainHeadError {
|
|
|
50
50
|
}
|
|
51
51
|
export class ChainHeadBlockNotPinnedError extends ChainHeadError {
|
|
52
52
|
name = 'ChainHeadBlockNotPinnedError';
|
|
53
|
+
hash;
|
|
54
|
+
constructor(message, hash) {
|
|
55
|
+
super(message);
|
|
56
|
+
this.hash = hash;
|
|
57
|
+
}
|
|
53
58
|
}
|
|
54
59
|
export class ChainHeadBlockPrunedError extends ChainHeadError {
|
|
55
60
|
name = 'ChainHeadBlockPrunedError';
|
package/json-rpc/group/index.js
CHANGED
|
@@ -14,4 +14,8 @@ export const subscriptionsInfo = {
|
|
|
14
14
|
'transactionWatch_unstable_unwatch',
|
|
15
15
|
],
|
|
16
16
|
transactionWatch_v1_submitAndWatch: ['transactionWatch_v1_watchEvent', 'transactionWatch_v1_unwatch'],
|
|
17
|
+
archive_v1_storage: ['archive_v1_storageEvent', 'archive_v1_stopStorage'],
|
|
18
|
+
archive_v1_storageDiff: ['archive_v1_storageDiffEvent', 'archive_v1_stopStorageDiff'],
|
|
19
|
+
archive_unstable_storage: ['archive_unstable_storageEvent', 'archive_unstable_stopStorage'],
|
|
20
|
+
archive_unstable_storageDiff: ['archive_unstable_storageDiffEvent', 'archive_unstable_stopStorageDiff'],
|
|
17
21
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dedot/api",
|
|
3
|
-
"version": "0.15.
|
|
3
|
+
"version": "0.15.3-next.65898ecf.10+65898ecf",
|
|
4
4
|
"description": "A delightful JavaScript/TypeScript client for Polkadot & Substrate",
|
|
5
5
|
"author": "Thang X. Vu <thang@dedot.dev>",
|
|
6
6
|
"homepage": "https://dedot.dev",
|
|
@@ -13,13 +13,13 @@
|
|
|
13
13
|
"type": "module",
|
|
14
14
|
"sideEffects": false,
|
|
15
15
|
"dependencies": {
|
|
16
|
-
"@dedot/codecs": "0.15.
|
|
17
|
-
"@dedot/providers": "0.15.
|
|
18
|
-
"@dedot/runtime-specs": "0.15.
|
|
19
|
-
"@dedot/shape": "0.15.
|
|
20
|
-
"@dedot/storage": "0.15.
|
|
21
|
-
"@dedot/types": "0.15.
|
|
22
|
-
"@dedot/utils": "0.15.
|
|
16
|
+
"@dedot/codecs": "0.15.3-next.65898ecf.10+65898ecf",
|
|
17
|
+
"@dedot/providers": "0.15.3-next.65898ecf.10+65898ecf",
|
|
18
|
+
"@dedot/runtime-specs": "0.15.3-next.65898ecf.10+65898ecf",
|
|
19
|
+
"@dedot/shape": "0.15.3-next.65898ecf.10+65898ecf",
|
|
20
|
+
"@dedot/storage": "0.15.3-next.65898ecf.10+65898ecf",
|
|
21
|
+
"@dedot/types": "0.15.3-next.65898ecf.10+65898ecf",
|
|
22
|
+
"@dedot/utils": "0.15.3-next.65898ecf.10+65898ecf"
|
|
23
23
|
},
|
|
24
24
|
"scripts": {
|
|
25
25
|
"build": "tsc --project tsconfig.build.json && tsc --project tsconfig.build.cjs.json",
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"node": ">=18"
|
|
49
49
|
},
|
|
50
50
|
"license": "Apache-2.0",
|
|
51
|
-
"gitHead": "
|
|
51
|
+
"gitHead": "65898ecf2226162fc6632d3f4f64ad5fe1179643",
|
|
52
52
|
"module": "./index.js",
|
|
53
53
|
"types": "./index.d.ts"
|
|
54
54
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
+
import { ISubstrateClient, ISubstrateClientAt } from '@dedot/api/types';
|
|
1
2
|
import { BlockHash, StorageData, StorageKey } from '@dedot/codecs';
|
|
2
|
-
import type { Callback,
|
|
3
|
-
import type { SubstrateApi } from '../chaintypes/index.js';
|
|
4
|
-
import { BaseSubstrateClient } from '../client/BaseSubstrateClient.js';
|
|
3
|
+
import type { Callback, Unsub } from '@dedot/types';
|
|
5
4
|
/**
|
|
6
5
|
* @name BaseStorageQuery
|
|
7
6
|
* @description
|
|
@@ -16,12 +15,12 @@ import { BaseSubstrateClient } from '../client/BaseSubstrateClient.js';
|
|
|
16
15
|
* This abstraction eliminates code duplication between different client implementations
|
|
17
16
|
* and provides a consistent interface for storage operations.
|
|
18
17
|
*/
|
|
19
|
-
export declare abstract class BaseStorageQuery
|
|
20
|
-
protected client:
|
|
18
|
+
export declare abstract class BaseStorageQuery {
|
|
19
|
+
protected client: ISubstrateClientAt<any> | ISubstrateClient<any, any>;
|
|
21
20
|
/**
|
|
22
21
|
* @param client - The substrate client instance
|
|
23
22
|
*/
|
|
24
|
-
constructor(client:
|
|
23
|
+
protected constructor(client: ISubstrateClientAt<any> | ISubstrateClient<any, any>);
|
|
25
24
|
/**
|
|
26
25
|
* Query multiple storage items in a single call
|
|
27
26
|
*
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { BlockHash, StorageData, StorageKey } from '@dedot/codecs';
|
|
2
|
-
import type { Callback,
|
|
3
|
-
import type { SubstrateApi } from '../chaintypes/index.js';
|
|
2
|
+
import type { Callback, Unsub } from '@dedot/types';
|
|
4
3
|
import { LegacyClient } from '../client/LegacyClient.js';
|
|
5
4
|
import { BaseStorageQuery } from './BaseStorageQuery.js';
|
|
6
5
|
/**
|
|
@@ -15,7 +14,9 @@ import { BaseStorageQuery } from './BaseStorageQuery.js';
|
|
|
15
14
|
* - Subscriptions using state_subscribeStorage
|
|
16
15
|
* - Efficient change tracking for subscriptions
|
|
17
16
|
*/
|
|
18
|
-
export declare class LegacyStorageQuery
|
|
17
|
+
export declare class LegacyStorageQuery extends BaseStorageQuery {
|
|
18
|
+
protected client: LegacyClient<any>;
|
|
19
|
+
constructor(client: LegacyClient<any>);
|
|
19
20
|
/**
|
|
20
21
|
* Query multiple storage items in a single call using state_queryStorageAt
|
|
21
22
|
*
|
|
@@ -12,6 +12,11 @@ import { BaseStorageQuery } from './BaseStorageQuery.js';
|
|
|
12
12
|
* - Efficient change tracking for subscriptions
|
|
13
13
|
*/
|
|
14
14
|
export class LegacyStorageQuery extends BaseStorageQuery {
|
|
15
|
+
client;
|
|
16
|
+
constructor(client) {
|
|
17
|
+
super(client);
|
|
18
|
+
this.client = client;
|
|
19
|
+
}
|
|
15
20
|
/**
|
|
16
21
|
* Query multiple storage items in a single call using state_queryStorageAt
|
|
17
22
|
*
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
+
import { DedotClient } from '@dedot/api/client';
|
|
1
2
|
import { BlockHash, StorageData, StorageKey } from '@dedot/codecs';
|
|
2
|
-
import type { Callback,
|
|
3
|
-
import type { SubstrateApi } from '../chaintypes/index.js';
|
|
4
|
-
import { DedotClient } from '../client/DedotClient.js';
|
|
3
|
+
import type { Callback, Unsub } from '@dedot/types';
|
|
5
4
|
import { BaseStorageQuery } from './BaseStorageQuery.js';
|
|
6
5
|
/**
|
|
7
6
|
* @name NewStorageQuery
|
|
@@ -15,7 +14,9 @@ import { BaseStorageQuery } from './BaseStorageQuery.js';
|
|
|
15
14
|
* - Subscriptions using chainHead 'bestBlock' events
|
|
16
15
|
* - Efficient change detection for subscriptions
|
|
17
16
|
*/
|
|
18
|
-
export declare class NewStorageQuery
|
|
17
|
+
export declare class NewStorageQuery extends BaseStorageQuery {
|
|
18
|
+
protected client: DedotClient<any>;
|
|
19
|
+
constructor(client: DedotClient<any>);
|
|
19
20
|
/**
|
|
20
21
|
* Query multiple storage items in a single call using chainHead_storage
|
|
21
22
|
*
|