@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.
Files changed (47) hide show
  1. package/chaintypes/substrate/index.d.ts +21 -1
  2. package/chaintypes/substrate/index.js +0 -1
  3. package/cjs/chaintypes/substrate/index.js +0 -1
  4. package/cjs/client/DedotClient.js +39 -6
  5. package/cjs/executor/Executor.js +1 -0
  6. package/cjs/executor/v2/StorageQueryExecutorV2.js +1 -3
  7. package/cjs/executor/v2/TxExecutorV2.js +5 -3
  8. package/cjs/json-rpc/group/Archive.js +226 -0
  9. package/cjs/json-rpc/group/ChainHead/ChainHead.js +121 -44
  10. package/cjs/json-rpc/group/ChainHead/error.js +5 -0
  11. package/cjs/json-rpc/group/index.js +1 -0
  12. package/cjs/json-rpc/subscriptionsInfo.js +4 -0
  13. package/cjs/storage/LegacyStorageQuery.js +5 -0
  14. package/cjs/storage/NewStorageQuery.js +5 -0
  15. package/client/BaseSubstrateClient.d.ts +3 -3
  16. package/client/DedotClient.d.ts +6 -4
  17. package/client/DedotClient.js +42 -9
  18. package/client/LegacyClient.d.ts +2 -2
  19. package/executor/Executor.d.ts +5 -5
  20. package/executor/Executor.js +1 -0
  21. package/executor/StorageQueryExecutor.d.ts +2 -2
  22. package/executor/v2/RuntimeApiExecutorV2.d.ts +2 -2
  23. package/executor/v2/StorageQueryExecutorV2.d.ts +4 -4
  24. package/executor/v2/StorageQueryExecutorV2.js +1 -3
  25. package/executor/v2/TxExecutorV2.d.ts +2 -1
  26. package/executor/v2/TxExecutorV2.js +5 -3
  27. package/executor/v2/ViewFunctionExecutorV2.d.ts +2 -2
  28. package/extrinsic/extensions/SignedExtension.d.ts +3 -3
  29. package/extrinsic/submittable/BaseSubmittableExtrinsic.d.ts +2 -2
  30. package/extrinsic/submittable/SubmittableExtrinsicV2.d.ts +2 -2
  31. package/json-rpc/group/Archive.d.ts +134 -0
  32. package/json-rpc/group/Archive.js +222 -0
  33. package/json-rpc/group/ChainHead/ChainHead.d.ts +10 -0
  34. package/json-rpc/group/ChainHead/ChainHead.js +121 -44
  35. package/json-rpc/group/ChainHead/error.d.ts +3 -0
  36. package/json-rpc/group/ChainHead/error.js +5 -0
  37. package/json-rpc/group/index.d.ts +1 -0
  38. package/json-rpc/group/index.js +1 -0
  39. package/json-rpc/subscriptionsInfo.js +4 -0
  40. package/package.json +9 -9
  41. package/storage/BaseStorageQuery.d.ts +5 -6
  42. package/storage/LegacyStorageQuery.d.ts +4 -3
  43. package/storage/LegacyStorageQuery.js +5 -0
  44. package/storage/NewStorageQuery.d.ts +5 -4
  45. package/storage/NewStorageQuery.js +5 -0
  46. package/storage/QueryableStorage.d.ts +2 -2
  47. package/types.d.ts +10 -5
@@ -1,4 +1,5 @@
1
- import type { GenericSubstrateApi, RpcLegacy, RpcV2, RpcVersion } from '@dedot/types';
1
+ import type { MultiAddress } from '@dedot/codecs';
2
+ import type { GenericChainKnownTypes, GenericSubstrateApi, RpcLegacy, RpcV2, RpcVersion } from '@dedot/types';
2
3
  import { ChainConsts } from './consts.js';
3
4
  import { ChainErrors } from './errors.js';
4
5
  import { ChainEvents } from './events.js';
@@ -6,8 +7,26 @@ import { ChainJsonRpcApis } from './json-rpc.js';
6
7
  import { ChainStorage } from './query.js';
7
8
  import { RuntimeApis } from './runtime.js';
8
9
  import { ChainTx } from './tx.js';
10
+ import type { FrameMetadataHashExtensionCheckMetadataHash, FrameSystemExtensionsCheckGenesis, FrameSystemExtensionsCheckMortality, FrameSystemExtensionsCheckNonZeroSender, FrameSystemExtensionsCheckNonce, FrameSystemExtensionsCheckSpecVersion, FrameSystemExtensionsCheckTxVersion, FrameSystemExtensionsCheckWeight, FrameSystemExtensionsWeightReclaim, KitchensinkRuntimeRuntimeCall, PalletAssetConversionTxPaymentChargeAssetTxPayment, SpRuntimeMultiSignature } from './types.js';
9
11
  import { ChainViewFunctions } from './view-functions.js';
10
12
  export * from './types.js';
13
+ interface ChainKnownTypes extends GenericChainKnownTypes {
14
+ Address: MultiAddress;
15
+ Signature: SpRuntimeMultiSignature;
16
+ RuntimeCall: KitchensinkRuntimeRuntimeCall;
17
+ Extra: [
18
+ FrameSystemExtensionsCheckNonZeroSender,
19
+ FrameSystemExtensionsCheckSpecVersion,
20
+ FrameSystemExtensionsCheckTxVersion,
21
+ FrameSystemExtensionsCheckGenesis,
22
+ FrameSystemExtensionsCheckMortality,
23
+ FrameSystemExtensionsCheckNonce,
24
+ FrameSystemExtensionsCheckWeight,
25
+ PalletAssetConversionTxPaymentChargeAssetTxPayment,
26
+ FrameMetadataHashExtensionCheckMetadataHash,
27
+ FrameSystemExtensionsWeightReclaim
28
+ ];
29
+ }
11
30
  export interface VersionedSubstrateApi<Rv extends RpcVersion> extends GenericSubstrateApi<Rv> {
12
31
  rpc: ChainJsonRpcApis<Rv>;
13
32
  consts: ChainConsts<Rv>;
@@ -17,6 +36,7 @@ export interface VersionedSubstrateApi<Rv extends RpcVersion> extends GenericSub
17
36
  call: RuntimeApis<Rv>;
18
37
  view: ChainViewFunctions<Rv>;
19
38
  tx: ChainTx<Rv>;
39
+ types: ChainKnownTypes;
20
40
  }
21
41
  /**
22
42
  * @name: SubstrateApi
@@ -1,2 +1 @@
1
- // Generated by dedot cli
2
1
  export * from './types.js';
@@ -1,5 +1,4 @@
1
1
  "use strict";
2
- // Generated by dedot cli
3
2
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
4
3
  if (k2 === undefined) k2 = k;
5
4
  var desc = Object.getOwnPropertyDescriptor(m, k);
@@ -19,6 +19,7 @@ class DedotClient// prettier-end-here
19
19
  extends BaseSubstrateClient_js_1.BaseSubstrateClient {
20
20
  _chainHead;
21
21
  _chainSpec;
22
+ _archive;
22
23
  _txBroadcaster;
23
24
  #apiAtCache = {};
24
25
  /**
@@ -51,6 +52,11 @@ class DedotClient// prettier-end-here
51
52
  get chainHead() {
52
53
  return (0, BaseSubstrateClient_js_1.ensurePresence)(this._chainHead);
53
54
  }
55
+ async archive() {
56
+ (0, utils_1.assert)(this._archive, 'Archive instance is not initialized');
57
+ (0, utils_1.assert)(await this._archive.supported(), 'Archive JSON-RPC is not supported by the connected server');
58
+ return this._archive;
59
+ }
54
60
  get txBroadcaster() {
55
61
  this.chainHead; // Ensure chain head is initialized
56
62
  (0, utils_1.assert)(this._txBroadcaster, 'JSON-RPC method to broadcast transactions is not supported by the server/node.');
@@ -71,6 +77,12 @@ class DedotClient// prettier-end-here
71
77
  const rpcMethods = (await this.rpc.rpc_methods()).methods;
72
78
  this._chainHead = new index_js_2.ChainHead(this, { rpcMethods });
73
79
  this._chainSpec = new index_js_2.ChainSpec(this, { rpcMethods });
80
+ // Always initialize Archive, but only set up fallback if supported
81
+ this._archive = new index_js_2.Archive(this, { rpcMethods });
82
+ // Set up ChainHead with Archive fallback only if Archive is supported
83
+ if (await this._archive.supported()) {
84
+ this._chainHead.withArchive(this._archive);
85
+ }
74
86
  this._txBroadcaster = await this.#initializeTxBroadcaster(rpcMethods);
75
87
  // Fetching node information
76
88
  let [_, genesisHash] = await Promise.all([
@@ -133,6 +145,7 @@ class DedotClient// prettier-end-here
133
145
  super.cleanUp();
134
146
  this._chainHead = undefined;
135
147
  this._chainSpec = undefined;
148
+ this._archive = undefined;
136
149
  this._txBroadcaster = undefined;
137
150
  this.#apiAtCache = {};
138
151
  }
@@ -159,19 +172,39 @@ class DedotClient// prettier-end-here
159
172
  }
160
173
  /**
161
174
  * Get a new API instance at a specific block hash
162
- * For now, this only supports pinned block hashes from the chain head
175
+ * Supports both pinned blocks (via ChainHead) and historical blocks (via Archive fallback)
163
176
  *
164
177
  * @param hash
165
178
  */
166
179
  async at(hash) {
167
180
  if (this.#apiAtCache[hash])
168
181
  return this.#apiAtCache[hash];
182
+ let targetVersion;
183
+ // Try to get block info from ChainHead first (for pinned blocks)
169
184
  const targetBlock = this.chainHead.findBlock(hash);
170
- (0, utils_1.assert)(targetBlock, 'Block is not pinned!');
171
- let targetVersion = targetBlock.runtime;
172
- if (!targetVersion) {
173
- // fallback to fetching on-chain runtime if we can't find it in the block
174
- targetVersion = this.toSubstrateRuntimeVersion(await this.callAt(hash).core.version());
185
+ if (targetBlock) {
186
+ targetVersion = targetBlock.runtime;
187
+ if (!targetVersion) {
188
+ // fallback to fetching on-chain runtime if we can't find it in the block
189
+ targetVersion = this.toSubstrateRuntimeVersion(await this.callAt(hash).core.version());
190
+ }
191
+ }
192
+ else {
193
+ // Block not pinned, try via Archive fallback if supported
194
+ if (this._archive && (await this._archive.supported())) {
195
+ console.warn(`Block ${hash} is not pinned, using Archive for historical access`);
196
+ try {
197
+ // Fetch runtime version via Archive
198
+ const runtimeRaw = await this._archive.call('Core_version', '0x', hash);
199
+ targetVersion = this.toSubstrateRuntimeVersion(codecs_1.$RuntimeVersion.tryDecode(runtimeRaw));
200
+ }
201
+ catch (error) {
202
+ throw new utils_1.DedotError(`Unable to fetch runtime version for block ${hash}: ${error}`);
203
+ }
204
+ }
205
+ else {
206
+ throw new utils_1.DedotError('Block is not pinned and Archive JSON-RPC is not supported by the server/node!');
207
+ }
175
208
  }
176
209
  let metadata = this.metadata;
177
210
  let registry = this.registry;
@@ -14,6 +14,7 @@ class Executor {
14
14
  this.#atBlockHash = atBlockHash;
15
15
  }
16
16
  get atBlockHash() {
17
+ // @ts-ignore
17
18
  return this.#atBlockHash || this.client.atBlockHash;
18
19
  }
19
20
  get registry() {
@@ -22,9 +22,7 @@ class StorageQueryExecutorV2 extends StorageQueryExecutor_js_1.StorageQueryExecu
22
22
  const entries = async (...args) => {
23
23
  const withArgs = !!args && args.length > 0;
24
24
  const key = withArgs ? entry.encodeKey(args, true) : entry.prefixKey;
25
- const results = await this.chainHead.storage([
26
- { type: 'descendantsValues', key },
27
- ]);
25
+ const results = await this.chainHead.storage([{ type: 'descendantsValues', key }]);
28
26
  return results.map(({ key, value }) => [
29
27
  entry.decodeKey(key),
30
28
  entry.decodeValue(value),
@@ -9,9 +9,11 @@ const TxExecutor_js_1 = require("../TxExecutor.js");
9
9
  * @description Execute a transaction instruction, returns a submittable extrinsic
10
10
  */
11
11
  class TxExecutorV2 extends TxExecutor_js_1.TxExecutor {
12
- constructor(api) {
13
- (0, utils_1.assert)(api.rpcVersion === 'v2', 'Only supports JSON-RPC v2');
14
- super(api);
12
+ client;
13
+ constructor(client) {
14
+ (0, utils_1.assert)(client.rpcVersion === 'v2', 'Only supports JSON-RPC v2');
15
+ super(client);
16
+ this.client = client;
15
17
  }
16
18
  createExtrinsic(call) {
17
19
  return new index_js_1.SubmittableExtrinsicV2(this.client, call);
@@ -0,0 +1,226 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Archive = void 0;
4
+ const utils_1 = require("@dedot/utils");
5
+ const JsonRpcGroup_js_1 = require("./JsonRpcGroup.js");
6
+ /**
7
+ * @name Archive
8
+ * Archive JSON-RPC methods for accessing historical blockchain data.
9
+ * Functions with the `archive` prefix allow obtaining the state of the chain
10
+ * at any point in the present or in the past.
11
+ *
12
+ * JSON-RPC V2: https://paritytech.github.io/json-rpc-interface-spec/api/archive.html
13
+ */
14
+ class Archive extends JsonRpcGroup_js_1.JsonRpcGroup {
15
+ #genesisHash;
16
+ #cache;
17
+ constructor(client, options) {
18
+ super(client, { prefix: 'archive', supportedVersions: ['unstable', 'v1'], ...options });
19
+ this.#cache = new Map();
20
+ }
21
+ /**
22
+ * Retrieves the body (list of transactions) of a given block.
23
+ * Returns an array of strings containing the hexadecimal-encoded SCALE-codec-encoded
24
+ * transactions in that block. If no block with that hash is found, null.
25
+ *
26
+ * @param hash - The block hash (optional, defaults to current finalized block)
27
+ * @returns Array of transaction hashes or null if block not found
28
+ *
29
+ * @example
30
+ * ```typescript
31
+ * // Get transactions from current finalized block
32
+ * const transactions = await archive.body();
33
+ *
34
+ * // Get transactions from specific block
35
+ * const transactions = await archive.body('0x1234...');
36
+ * ```
37
+ */
38
+ async body(hash) {
39
+ const blockHash = hash || (await this.finalizedHash());
40
+ const cacheKey = `${blockHash}::body`;
41
+ if (this.#cache.has(cacheKey)) {
42
+ return this.#cache.get(cacheKey);
43
+ }
44
+ const result = await this.send('body', blockHash);
45
+ this.#cache.set(cacheKey, result);
46
+ return result;
47
+ }
48
+ /**
49
+ * Get the chain's genesis hash.
50
+ * Returns a string containing the hexadecimal-encoded hash of the genesis block of the chain.
51
+ * This value is cached after the first call.
52
+ *
53
+ * @returns The genesis block hash
54
+ */
55
+ async genesisHash() {
56
+ if (!this.#genesisHash) {
57
+ this.#genesisHash = await this.send('genesisHash');
58
+ }
59
+ return this.#genesisHash;
60
+ }
61
+ /**
62
+ * Get the block's header.
63
+ * Returns a string containing the hexadecimal-encoded SCALE-codec encoding header of the block.
64
+ *
65
+ * @param hash - The block hash (optional, defaults to current finalized block)
66
+ * @returns The encoded block header or null if block not found
67
+ *
68
+ * @example
69
+ * ```typescript
70
+ * // Get header of current finalized block
71
+ * const header = await archive.header();
72
+ *
73
+ * // Get header of specific block
74
+ * const header = await archive.header('0x1234...');
75
+ * ```
76
+ */
77
+ async header(hash) {
78
+ const blockHash = hash || (await this.finalizedHash());
79
+ const cacheKey = `${blockHash}::header`;
80
+ if (this.#cache.has(cacheKey)) {
81
+ return this.#cache.get(cacheKey);
82
+ }
83
+ const result = await this.send('header', blockHash);
84
+ this.#cache.set(cacheKey, result);
85
+ return result;
86
+ }
87
+ /**
88
+ * Get the height of the current finalized block.
89
+ * Returns an integer height of the current finalized block of the chain.
90
+ *
91
+ * @returns The height of the finalized block
92
+ */
93
+ async finalizedHeight() {
94
+ return this.send('finalizedHeight');
95
+ }
96
+ /**
97
+ * Get the hash of the current finalized block.
98
+ * Returns a string containing the hexadecimal-encoded hash of the current finalized block.
99
+ * This is a convenience method that combines finalizedHeight() and hashByHeight().
100
+ *
101
+ * @returns The hash of the current finalized block
102
+ */
103
+ async finalizedHash() {
104
+ const height = await this.finalizedHeight();
105
+ const hashes = await this.hashByHeight(height);
106
+ if (hashes.length === 0) {
107
+ throw new Error(`No block found at finalized height ${height}`);
108
+ }
109
+ return hashes[0];
110
+ }
111
+ /**
112
+ * Get the hashes of blocks from the given height.
113
+ * Returns an array (possibly empty) of strings containing hexadecimal-encoded hashes of block headers.
114
+ *
115
+ * Note: For heights <= finalized height, there is guaranteed to be one block.
116
+ * For heights > finalized height, there may be zero, one or multiple blocks depending on forks.
117
+ *
118
+ * @param height - The block height
119
+ * @returns Array of block hashes at the given height
120
+ */
121
+ async hashByHeight(height) {
122
+ return this.send('hashByHeight', height);
123
+ }
124
+ /**
125
+ * Call into the Runtime API at a specified block's state.
126
+ *
127
+ * @param func - The runtime API function to call
128
+ * @param params - The parameters for the function call (SCALE-encoded)
129
+ * @param hash - The block hash (optional, defaults to current finalized block)
130
+ * @returns The result of the runtime call
131
+ *
132
+ * @example
133
+ * ```typescript
134
+ * // Call Core_version on current finalized block
135
+ * const version = await archive.call('Core_version', '0x');
136
+ *
137
+ * // Call Core_version on specific block
138
+ * const version = await archive.call('Core_version', '0x', '0x1234...');
139
+ * ```
140
+ */
141
+ async call(func, params, hash) {
142
+ const blockHash = hash || (await this.finalizedHash());
143
+ const cacheKey = `${blockHash}::call::${func}::${params}`;
144
+ if (this.#cache.has(cacheKey)) {
145
+ return this.#cache.get(cacheKey);
146
+ }
147
+ const result = await this.send('call', blockHash, func, params);
148
+ if (!result.success) {
149
+ throw new utils_1.DedotError(result.error);
150
+ }
151
+ this.#cache.set(cacheKey, result.value);
152
+ return result.value;
153
+ }
154
+ /**
155
+ * Returns storage entries at a specific block's state via subscription.
156
+ *
157
+ * @param items - Array of storage queries with optional pagination
158
+ * @param childTrie - Optional child trie key
159
+ * @param callback - Callback to receive storage events
160
+ * @param hash - The block hash (optional, defaults to current finalized block)
161
+ * @returns Unsubscribe function
162
+ */
163
+ async #storageSubscription(items, childTrie, callback, hash) {
164
+ const blockHash = hash || (await this.finalizedHash());
165
+ return this.send('storage', blockHash, items, childTrie, callback);
166
+ }
167
+ /**
168
+ * Returns storage entries at a specific block's state.
169
+ * This method collects all storage events and returns them as a single result.
170
+ *
171
+ * @param items - Array of storage queries with optional pagination
172
+ * @param childTrie - Optional child trie key
173
+ * @param hash - The block hash (optional, defaults to current finalized block)
174
+ * @returns Storage results array
175
+ *
176
+ * @example
177
+ * ```typescript
178
+ * // Query storage from current finalized block
179
+ * const results = await archive.storage([{ key: '0x1234', type: 'value' }]);
180
+ *
181
+ * // Query storage from specific block
182
+ * const results = await archive.storage([{ key: '0x1234', type: 'value' }], null, '0xabcd...');
183
+ * ```
184
+ */
185
+ async storage(items, childTrie, hash) {
186
+ return new Promise(async (resolve, reject) => {
187
+ const results = [];
188
+ const blockHash = hash || (await this.finalizedHash());
189
+ // Generate cache key
190
+ const cacheKey = `${blockHash}::storage::${JSON.stringify(items)}::${childTrie ?? null}`;
191
+ // Check cache
192
+ if (this.#cache.has(cacheKey)) {
193
+ return resolve(this.#cache.get(cacheKey));
194
+ }
195
+ this.#storageSubscription(items, childTrie || null, (event) => {
196
+ switch (event.event) {
197
+ case 'storage':
198
+ results.push(event);
199
+ break;
200
+ case 'storageDone':
201
+ // Set cache after successful completion
202
+ this.#cache.set(cacheKey, results);
203
+ resolve(results);
204
+ break;
205
+ case 'storageError':
206
+ reject(new utils_1.DedotError(event.error));
207
+ break;
208
+ }
209
+ }, blockHash).catch(reject);
210
+ });
211
+ }
212
+ /**
213
+ * Clears the internal cache used for storing archive query results.
214
+ * This can be useful for memory management or when you want to force fresh data retrieval.
215
+ *
216
+ * @example
217
+ * ```typescript
218
+ * // Clear all cached results
219
+ * archive.clearCache();
220
+ * ```
221
+ */
222
+ clearCache() {
223
+ this.#cache.clear();
224
+ }
225
+ }
226
+ exports.Archive = Archive;
@@ -23,6 +23,17 @@ class ChainHead extends JsonRpcGroup_js_1.JsonRpcGroup {
23
23
  #blockUsage;
24
24
  #cache;
25
25
  #operationQueue;
26
+ /**
27
+ * Archive instance used as fallback when ChainHead blocks are not pinned.
28
+ *
29
+ * When ChainHead operations fail with ChainHeadBlockNotPinnedError, the system
30
+ * automatically attempts the same operation using the Archive API. This provides
31
+ * seamless access to historical blockchain data even when blocks are no longer
32
+ * maintained in the ChainHead's pinned block set.
33
+ *
34
+ * @private
35
+ */
36
+ #archive;
26
37
  constructor(client, options) {
27
38
  super(client, { prefix: 'chainHead', supportedVersions: ['unstable', 'v1'], ...options });
28
39
  this.#handlers = {};
@@ -36,6 +47,18 @@ class ChainHead extends JsonRpcGroup_js_1.JsonRpcGroup {
36
47
  // This helps us to not accidentally putting too much stress on the JSON-RPC server, especially smoldot/light-client
37
48
  this.#operationQueue = new utils_1.ThrottleQueue(this.#__unsafe__isSmoldot() ? 25 : 250);
38
49
  }
50
+ /**
51
+ * Attach an Archive instance as fallback for operations that fail due to unpinned blocks.
52
+ * When a ChainHeadBlockNotPinnedError occurs, the operation will automatically fallback
53
+ * to the Archive API to attempt to retrieve the data from historical blocks.
54
+ *
55
+ * @param archive - Archive instance to use as fallback
56
+ * @returns this ChainHead instance for method chaining
57
+ */
58
+ withArchive(archive) {
59
+ this.#archive = archive;
60
+ return this;
61
+ }
39
62
  async runtimeVersion() {
40
63
  await this.#ensureFollowed();
41
64
  return this.#finalizedRuntime;
@@ -344,11 +367,39 @@ class ChainHead extends JsonRpcGroup_js_1.JsonRpcGroup {
344
367
  return hash;
345
368
  }
346
369
  else {
347
- throw new error_js_1.ChainHeadBlockNotPinnedError(`Block hash ${hash} is not pinned`);
370
+ throw new error_js_1.ChainHeadBlockNotPinnedError(`Block hash ${hash} is not pinned`, hash);
348
371
  }
349
372
  }
350
373
  return (0, utils_1.ensurePresence)(this.#bestHash || this.#finalizedHash);
351
374
  }
375
+ /**
376
+ * Executes a ChainHead operation with automatic Archive fallback.
377
+ *
378
+ * This method first attempts the primary ChainHead operation. If it fails with
379
+ * ChainHeadBlockNotPinnedError (indicating the block is no longer pinned), and
380
+ * an Archive instance is available, it automatically retries the operation using
381
+ * the Archive API.
382
+ *
383
+ * @param operation - Primary ChainHead operation to attempt
384
+ * @param fallback - Archive operation to fallback to
385
+ * @param hash - Block hash being accessed (for logging)
386
+ * @returns Result from either ChainHead or Archive operation
387
+ * @throws Original error if not a pinning error or no Archive available
388
+ * @private
389
+ */
390
+ async #tryWithArchive(operation, fallback) {
391
+ try {
392
+ return await operation();
393
+ }
394
+ catch (error) {
395
+ if (error instanceof error_js_1.ChainHeadBlockNotPinnedError && this.#archive) {
396
+ const errorHash = error.hash;
397
+ console.warn(`Block ${errorHash} not pinned in ChainHead, falling back to Archive`);
398
+ return await fallback(this.#archive, errorHash);
399
+ }
400
+ throw error;
401
+ }
402
+ }
352
403
  #getOperationHandler(result) {
353
404
  const handler = this.#handlers[result.operationId];
354
405
  if (handler)
@@ -478,21 +529,31 @@ class ChainHead extends JsonRpcGroup_js_1.JsonRpcGroup {
478
529
  async body(at) {
479
530
  await this.#ensureFollowed();
480
531
  const shouldRetryOnPrunedBlock = !at;
481
- try {
532
+ const operation = async () => {
482
533
  const atHash = this.#ensurePinnedHash(at);
483
534
  const cacheKey = `${atHash}::body`;
484
535
  if (this.#cache.has(cacheKey)) {
485
536
  return this.#cache.get(cacheKey);
486
537
  }
487
- const operation = async () => {
538
+ const bodyOperation = async () => {
488
539
  await this.#ensureFollowed();
489
540
  const hash = this.#ensurePinnedHash(atHash);
490
541
  const resp = await this.send('body', this.#subscriptionId, hash);
491
542
  return this.#awaitOperation(resp, hash);
492
543
  };
493
- const resp = await this.#operationQueue.add(() => this.#performOperationWithRetry(operation, atHash));
544
+ const resp = await this.#operationQueue.add(() => this.#performOperationWithRetry(bodyOperation, atHash));
494
545
  this.#cache.set(cacheKey, resp);
495
546
  return resp;
547
+ };
548
+ const fallback = async (archive, hash) => {
549
+ const result = await archive.body(hash);
550
+ if (result === undefined) {
551
+ throw new error_js_1.ChainHeadOperationError(`Block ${hash} not found in Archive`);
552
+ }
553
+ return result;
554
+ };
555
+ try {
556
+ return await this.#tryWithArchive(operation, fallback);
496
557
  }
497
558
  catch (e) {
498
559
  if (e instanceof error_js_1.ChainHeadBlockPrunedError && shouldRetryOnPrunedBlock) {
@@ -507,21 +568,25 @@ class ChainHead extends JsonRpcGroup_js_1.JsonRpcGroup {
507
568
  async call(func, params = '0x', at) {
508
569
  await this.#ensureFollowed();
509
570
  const shouldRetryOnPrunedBlock = !at;
510
- try {
571
+ const operation = async () => {
511
572
  const atHash = this.#ensurePinnedHash(at);
512
573
  const cacheKey = `${atHash}::call::${func}::${params}`;
513
574
  if (this.#cache.has(cacheKey)) {
514
575
  return this.#cache.get(cacheKey);
515
576
  }
516
- const operation = async () => {
577
+ const callOperation = async () => {
517
578
  await this.#ensureFollowed();
518
579
  const hash = this.#ensurePinnedHash(atHash);
519
580
  const resp = await this.send('call', this.#subscriptionId, hash, func, params);
520
581
  return this.#awaitOperation(resp, hash);
521
582
  };
522
- const resp = await this.#operationQueue.add(() => this.#performOperationWithRetry(operation, atHash));
583
+ const resp = await this.#operationQueue.add(() => this.#performOperationWithRetry(callOperation, atHash));
523
584
  this.#cache.set(cacheKey, resp);
524
585
  return resp;
586
+ };
587
+ const fallback = (archive, hash) => archive.call(func, params, hash);
588
+ try {
589
+ return await this.#tryWithArchive(operation, fallback);
525
590
  }
526
591
  catch (e) {
527
592
  if (e instanceof error_js_1.ChainHeadBlockPrunedError && shouldRetryOnPrunedBlock) {
@@ -535,14 +600,18 @@ class ChainHead extends JsonRpcGroup_js_1.JsonRpcGroup {
535
600
  */
536
601
  async header(at) {
537
602
  await this.#ensureFollowed();
538
- const hash = this.#ensurePinnedHash(at);
539
- const cacheKey = `${hash}::header`;
540
- if (this.#cache.has(cacheKey)) {
541
- return this.#cache.get(cacheKey);
542
- }
543
- const resp = await this.#getHeader(hash);
544
- this.#cache.set(cacheKey, resp);
545
- return resp;
603
+ const operation = async () => {
604
+ const hash = this.#ensurePinnedHash(at);
605
+ const cacheKey = `${hash}::header`;
606
+ if (this.#cache.has(cacheKey)) {
607
+ return this.#cache.get(cacheKey);
608
+ }
609
+ const resp = await this.#getHeader(hash);
610
+ this.#cache.set(cacheKey, resp);
611
+ return resp;
612
+ };
613
+ const fallback = (archive, errorHash) => archive.header(errorHash);
614
+ return await this.#tryWithArchive(operation, fallback);
546
615
  }
547
616
  async #getHeader(at) {
548
617
  return await this.send('header', this.#subscriptionId, at);
@@ -553,35 +622,46 @@ class ChainHead extends JsonRpcGroup_js_1.JsonRpcGroup {
553
622
  async storage(items, childTrie, at) {
554
623
  await this.#ensureFollowed();
555
624
  const shouldRetryOnPrunedBlock = !at;
556
- const hash = this.#ensurePinnedHash(at);
557
- try {
558
- // JSON.stringify(items) might get big, we probably should do a twox hashing in such case
559
- const cacheKey = `${hash}::storage::${JSON.stringify(items)}::${childTrie ?? null}`;
560
- if (this.#cache.has(cacheKey)) {
561
- return this.#cache.get(cacheKey);
562
- }
563
- this.#blockUsage.use(hash);
564
- let results = [];
565
- if (this.#__unsafe__isSmoldot()) {
566
- const fetchItem = async (item) => {
567
- const [batch, newDiscardedItems] = await this.#getStorage([item], childTrie ?? null, hash);
568
- if (newDiscardedItems.length > 0) {
569
- return fetchItem(item);
625
+ const operation = async () => {
626
+ const hash = this.#ensurePinnedHash(at);
627
+ try {
628
+ // JSON.stringify(items) might get big, we probably should do a twox hashing in such case
629
+ const cacheKey = `${hash}::storage::${JSON.stringify(items)}::${childTrie ?? null}`;
630
+ if (this.#cache.has(cacheKey)) {
631
+ return this.#cache.get(cacheKey);
632
+ }
633
+ this.#blockUsage.use(hash);
634
+ let results = [];
635
+ if (this.#__unsafe__isSmoldot()) {
636
+ const fetchItem = async (item) => {
637
+ const [batch, newDiscardedItems] = await this.#getStorage([item], childTrie ?? null, hash);
638
+ if (newDiscardedItems.length > 0) {
639
+ return fetchItem(item);
640
+ }
641
+ return batch;
642
+ };
643
+ results = (await Promise.all(items.map((one) => fetchItem(one)))).flat();
644
+ }
645
+ else {
646
+ let queryItems = items;
647
+ while (queryItems.length > 0) {
648
+ const [newBatch, newDiscardedItems] = await this.#getStorage(queryItems, childTrie ?? null, hash);
649
+ results.push(...newBatch);
650
+ queryItems = newDiscardedItems;
570
651
  }
571
- return batch;
572
- };
573
- results = (await Promise.all(items.map((one) => fetchItem(one)))).flat();
574
- }
575
- else {
576
- let queryItems = items;
577
- while (queryItems.length > 0) {
578
- const [newBatch, newDiscardedItems] = await this.#getStorage(queryItems, childTrie ?? null, hash);
579
- results.push(...newBatch);
580
- queryItems = newDiscardedItems;
581
652
  }
653
+ this.#cache.set(cacheKey, results);
654
+ return results;
655
+ }
656
+ finally {
657
+ this.#blockUsage.release(hash);
582
658
  }
583
- this.#cache.set(cacheKey, results);
584
- return results;
659
+ };
660
+ const fallback = (archive, hash) => {
661
+ return archive.storage(items, childTrie, hash);
662
+ };
663
+ try {
664
+ return await this.#tryWithArchive(operation, fallback);
585
665
  }
586
666
  catch (e) {
587
667
  if (e instanceof error_js_1.ChainHeadBlockPrunedError && shouldRetryOnPrunedBlock) {
@@ -589,9 +669,6 @@ class ChainHead extends JsonRpcGroup_js_1.JsonRpcGroup {
589
669
  }
590
670
  throw e;
591
671
  }
592
- finally {
593
- this.#blockUsage.release(hash);
594
- }
595
672
  }
596
673
  async #getStorage(items, childTrie, at) {
597
674
  const operation = () => this.#getStorageOperation(items, childTrie, this.#ensurePinnedHash(at));
@@ -59,6 +59,11 @@ class ChainHeadInvalidRuntimeError extends ChainHeadError {
59
59
  exports.ChainHeadInvalidRuntimeError = ChainHeadInvalidRuntimeError;
60
60
  class ChainHeadBlockNotPinnedError extends ChainHeadError {
61
61
  name = 'ChainHeadBlockNotPinnedError';
62
+ hash;
63
+ constructor(message, hash) {
64
+ super(message);
65
+ this.hash = hash;
66
+ }
62
67
  }
63
68
  exports.ChainHeadBlockNotPinnedError = ChainHeadBlockNotPinnedError;
64
69
  class ChainHeadBlockPrunedError extends ChainHeadError {