@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
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type {
|
|
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
|
|
@@ -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
|
-
*
|
|
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
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
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;
|
package/cjs/executor/Executor.js
CHANGED
|
@@ -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
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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
|
-
|
|
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
|
|
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(
|
|
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
|
-
|
|
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
|
|
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(
|
|
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
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
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
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
const
|
|
568
|
-
|
|
569
|
-
|
|
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
|
-
|
|
584
|
-
|
|
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 {
|